/*
 Navicat Premium Data Transfer

 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 50643
 Source Host           : localhost:3306
 Source Schema         : chuyun_blog

 Target Server Type    : MySQL
 Target Server Version : 50643
 File Encoding         : 65001

 Date: 04/04/2020 11:39:36
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for black_word
-- ----------------------------
DROP TABLE IF EXISTS `black_word`;
CREATE TABLE `black_word` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `content` varchar(100) NOT NULL,
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of black_word
-- ----------------------------
BEGIN;
INSERT INTO `black_word` VALUES (1, '博彩', 0, NULL, '2020-04-04 11:07:41', NULL, '2020-04-04 11:07:41');
INSERT INTO `black_word` VALUES (2, '老虎机', 0, NULL, '2020-04-04 11:07:50', NULL, '2020-04-04 11:07:50');
INSERT INTO `black_word` VALUES (3, '枪支', 0, NULL, '2020-04-04 11:07:54', NULL, '2020-04-04 11:07:54');
INSERT INTO `black_word` VALUES (4, '红包', 0, NULL, '2020-04-04 11:07:58', NULL, '2020-04-04 11:07:58');
INSERT INTO `black_word` VALUES (5, '彩票', 0, NULL, '2020-04-04 11:08:02', NULL, '2020-04-04 11:08:02');
INSERT INTO `black_word` VALUES (6, '优惠券', 0, NULL, '2020-04-04 11:12:20', NULL, '2020-04-04 11:12:20');
INSERT INTO `black_word` VALUES (7, '成人', 0, NULL, '2020-04-04 11:15:43', NULL, '2020-04-04 11:15:43');
INSERT INTO `black_word` VALUES (8, '澳门', 0, NULL, '2020-04-04 11:15:49', NULL, '2020-04-04 11:15:49');
INSERT INTO `black_word` VALUES (9, '共产党', 0, NULL, '2020-04-04 11:15:53', NULL, '2020-04-04 11:15:53');
COMMIT;

-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `cate_name` varchar(100) NOT NULL,
  `cate_sort` int(11) NOT NULL DEFAULT '1',
  `cate_desc` varchar(100) DEFAULT NULL,
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of category
-- ----------------------------
BEGIN;
INSERT INTO `category` VALUES (1, '学车心得', 100, '分享你的学车心得', 1, NULL, '2020-03-09 17:22:00', NULL, '2020-03-12 18:09:05');
INSERT INTO `category` VALUES (2, '驾考宝典', 90, '分享你的考试技巧', 1, NULL, '2020-03-09 17:22:47', NULL, '2020-03-12 18:09:01');
INSERT INTO `category` VALUES (3, '驾校服务', 80, '驾校服务活动', 1, NULL, '2020-03-09 17:28:41', NULL, '2020-03-12 18:08:25');
INSERT INTO `category` VALUES (4, '活动专版', 70, '活动专区', 1, NULL, '2020-03-09 17:36:30', NULL, '2020-03-12 18:08:21');
INSERT INTO `category` VALUES (5, '吐槽大会', 60, '吐槽大会，吐槽一切，随心所欲', 1, NULL, '2020-03-09 17:39:07', NULL, '2020-03-12 18:08:58');
INSERT INTO `category` VALUES (6, '视频专版', 50, '发布短视频，快速学车', 1, NULL, '2020-03-09 17:40:38', NULL, '2020-03-12 18:09:11');
INSERT INTO `category` VALUES (8, '前端开发', 1, '分享前端相关技术：H5,VueJS,React,NodeJS', 0, NULL, '2020-03-12 15:17:58', NULL, '2020-03-12 15:17:58');
INSERT INTO `category` VALUES (9, '后端开发', 2, '分享后端开发技术：Java、SpringBoot、SpringCloud、分布式、高并发、面向对象', 0, NULL, '2020-03-12 15:18:56', NULL, '2020-03-12 15:20:38');
INSERT INTO `category` VALUES (10, '大数据', 3, '分享大数据相关技术：数据采集、数据挖掘、数据分析', 0, NULL, '2020-03-12 15:19:48', NULL, '2020-03-12 15:19:48');
INSERT INTO `category` VALUES (11, '人工智能', 4, '分析AI相关技术：计算机视觉、自然语言翻译、人脸识别、知识图谱', 0, NULL, '2020-03-12 15:27:12', NULL, '2020-03-12 15:27:12');
INSERT INTO `category` VALUES (12, '程序人生', 100, '分享程序人生的酸甜苦辣', 0, NULL, '2020-03-12 15:28:53', NULL, '2020-03-12 15:28:53');
INSERT INTO `category` VALUES (13, '计算机原理', 5, '分享学习计算机原理的成长过程', 0, NULL, '2020-03-12 15:30:01', NULL, '2020-03-12 15:30:01');
INSERT INTO `category` VALUES (14, '算法', 8, '分享数据结构和算法以及应用实践', 0, NULL, '2020-03-12 18:16:12', NULL, '2020-03-12 18:16:12');
COMMIT;

-- ----------------------------
-- Table structure for comment
-- ----------------------------
DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `comment_content` text NOT NULL,
  `comment_parent` bigint(20) NOT NULL,
  `user_id` int(11) NOT NULL,
  `post_id` bigint(20) NOT NULL,
  `path_trace` varchar(1000) DEFAULT NULL,
  `accept_user_id` bigint(20) NOT NULL DEFAULT '0',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_by` varchar(255) DEFAULT 'admin',
  `update_by` varchar(255) DEFAULT 'admin',
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `like_count` int(11) NOT NULL DEFAULT '0',
  `dislike_count` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `FKlhqkt5diooecok7509whj2li6` (`post_id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of comment
-- ----------------------------
BEGIN;
INSERT INTO `comment` VALUES (9, '赞！！！', 0, 9, 18, '/', 3, '2020-03-12 18:25:29', '2020-03-12 18:25:29', 'admin', 'admin', 0, 0, 0);
INSERT INTO `comment` VALUES (10, '<a href=\'#comment-id-9\'>@zhaoyun</a> zhaoyun: 再赞！', 9, 9, 18, '/9/', 9, '2020-03-12 18:25:42', '2020-03-12 18:25:42', 'admin', 'admin', 0, 0, 0);
INSERT INTO `comment` VALUES (11, '看来要学Kotlin啦！', 0, 1, 14, '/', 1, '2020-03-12 18:43:55', '2020-03-12 18:43:55', 'admin', 'admin', 0, 0, 0);
INSERT INTO `comment` VALUES (12, '<a href=\'#comment-id-11\'>@管理员</a> 管理员: 回复测试！', 11, 1, 14, '/11/', 1, '2020-03-12 18:44:07', '2020-03-12 18:44:07', 'admin', 'admin', 0, 0, 0);
INSERT INTO `comment` VALUES (13, '6666', 0, 3, 24, '/', 9, '2020-03-12 18:50:06', '2020-03-12 18:50:06', 'admin', 'admin', 0, 1, 0);
INSERT INTO `comment` VALUES (14, '优秀！', 0, 3, 22, '/', 9, '2020-03-12 22:32:41', '2020-03-12 22:32:41', 'admin', 'admin', 0, 1, 0);
INSERT INTO `comment` VALUES (15, '<a href=\'#comment-id-14\'>@张三</a> 张三: 哈哈！', 14, 9, 22, '/14/', 3, '2020-03-12 22:33:05', '2020-03-12 22:33:05', 'admin', 'admin', 0, 0, 0);
INSERT INTO `comment` VALUES (16, '<a href=\'#comment-id-13\'>@张三</a> 张三: 赞！！！', 13, 9, 24, '/13/', 3, '2020-03-14 15:19:56', '2020-03-14 15:19:56', 'admin', 'admin', 0, 0, 0);
COMMIT;

-- ----------------------------
-- Table structure for link
-- ----------------------------
DROP TABLE IF EXISTS `link`;
CREATE TABLE `link` (
  `id` bigint(20) NOT NULL,
  `link_desc` varchar(255) DEFAULT NULL,
  `link_name` varchar(255) NOT NULL,
  `link_pic` varchar(255) DEFAULT NULL,
  `link_url` varchar(255) NOT NULL,
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `url` varchar(200) NOT NULL,
  `resource_type` varchar(255) NOT NULL,
  `pid` bigint(20) NOT NULL,
  `icon` varchar(255) DEFAULT NULL,
  `del_flag` int(1) DEFAULT '0',
  `create_by` varchar(20) NOT NULL DEFAULT 'admin',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT 'admin',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `sort` double(11,0) DEFAULT '1',
  `target` varchar(20) DEFAULT '_self',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of permission
-- ----------------------------
BEGIN;
INSERT INTO `permission` VALUES (1, '首页', '/admin', 'menu', 0, 'fa fa-dashboard', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-16 12:42:25', 1, '_self');
INSERT INTO `permission` VALUES (6, '获得侧边栏菜单', '/admin/currentMenus', 'button', 1, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:45:28', 6, '_self');
INSERT INTO `permission` VALUES (70, '用户管理', '/admin/user', 'menu', 0, 'fa fa-user', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-03-07 20:07:52', 80, '_self');
INSERT INTO `permission` VALUES (72, '添加用户', '/admin/user/new', 'menu', 70, 'fa fa-circle-o', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-16 12:54:40', 72, '_self');
INSERT INTO `permission` VALUES (73, '用户保存', '/admin/user/save', 'button', 70, NULL, 0, 'admin', '2019-10-15 20:22:36', 'admin', '2019-10-15 20:30:55', 73, '_self');
INSERT INTO `permission` VALUES (74, '删除用户', '/admin/user/delete', 'button', 70, NULL, 0, 'admin', '2019-10-15 20:22:36', 'admin', '2019-10-15 20:30:55', 74, '_self');
INSERT INTO `permission` VALUES (75, '批量删除用户', '/admin/user/batchDelete', 'button', 70, NULL, 0, 'admin', '2019-10-15 20:22:36', 'admin', '2019-10-15 20:30:55', 75, '_self');
INSERT INTO `permission` VALUES (76, '编辑用户', '/admin/user/edit', 'button', 70, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-16 13:27:24', 76, '_self');
INSERT INTO `permission` VALUES (82, '保存个人信息', '/admin/user/profile/save', 'button', 120, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:43:51', 82, '_self');
INSERT INTO `permission` VALUES (83, '修改密码', '/admin/user/changePass', 'button', 120, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:43:41', 83, '_self');
INSERT INTO `permission` VALUES (91, '角色管理', '/admin/role', 'menu', 0, 'fa fa-snowflake-o', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-16 12:52:05', 91, '_self');
INSERT INTO `permission` VALUES (92, '保存角色', '/admin/role/save', 'button', 91, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:55:53', 92, '_self');
INSERT INTO `permission` VALUES (93, '编辑角色', '/admin/role/edit', 'page', 91, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:58:40', 93, '_self');
INSERT INTO `permission` VALUES (94, '删除角色', '/admin/role/delete', 'button', 91, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:55:47', 94, '_self');
INSERT INTO `permission` VALUES (95, '权限管理', '/admin/permission', 'menu', 0, 'fa fa-podcast', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-03-12 15:08:17', 95, '_self');
INSERT INTO `permission` VALUES (96, '保存权限', '/admin/permission/save', 'button', 95, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:55:45', 96, '_self');
INSERT INTO `permission` VALUES (97, '编辑权限', '/admin/permission/edit', 'page', 95, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:57:13', 97, '_self');
INSERT INTO `permission` VALUES (98, '删除权限', '/admin/permission/delete', 'button', 95, '', 0, 'admin', '2019-10-15 20:22:36', 'admin', '2020-02-07 23:55:43', 98, '_self');
INSERT INTO `permission` VALUES (106, '获得当前登录用户信息接口', '/admin/currentUser', 'button', 1, '', 0, 'admin', '2020-02-04 10:26:13', 'admin', '2020-02-07 23:37:08', 1, '_self');
INSERT INTO `permission` VALUES (110, '添加权限', '/admin/permission/new', 'menu', 95, 'fa fa-circle-o', 0, 'admin', '2020-02-07 23:14:11', 'admin', '2020-02-16 12:55:01', 1, '_self');
INSERT INTO `permission` VALUES (111, '添加角色', '/admin/role/new', 'menu', 91, 'fa fa-circle-o', 0, 'admin', '2020-02-07 23:19:05', 'admin', '2020-02-16 12:54:51', 1, '_self');
INSERT INTO `permission` VALUES (120, '个人信息', '/admin/user/profile', 'page', 0, '', 0, 'admin', '2020-02-07 23:38:51', 'admin', '2020-02-08 00:02:34', 99, '_self');
INSERT INTO `permission` VALUES (126, '用户列表', '/admin/user', 'menu', 70, 'fa fa-circle-o', 0, 'admin', '2020-02-08 19:20:23', 'admin', '2020-02-16 12:54:33', 0, '_self');
INSERT INTO `permission` VALUES (127, '角色列表', '/admin/role', 'menu', 91, 'fa fa-circle-o', 0, 'admin', '2020-02-08 19:20:54', 'admin', '2020-02-16 12:54:47', 0, '_self');
INSERT INTO `permission` VALUES (128, '权限列表', '/admin/permission', 'menu', 95, 'fa fa-circle-o', 0, 'admin', '2020-02-08 19:21:16', 'admin', '2020-02-16 12:54:57', 0, '_self');
INSERT INTO `permission` VALUES (131, '文章管理', '/admin/post', 'menu', 0, 'fa fa-paint-brush', 0, 'admin', '2020-03-07 19:37:26', 'admin', '2020-03-12 15:06:22', 5, '_self');
INSERT INTO `permission` VALUES (132, '发布文章', '/admin/post/new', 'menu', 131, 'fa fa-circle-o', 0, 'admin', '2020-03-07 19:39:34', 'admin', '2020-03-12 15:06:18', 9, '_self');
INSERT INTO `permission` VALUES (133, '文章列表', '/admin/post', 'menu', 131, 'fa fa-circle-o', 0, 'admin', '2020-03-07 19:40:00', 'admin', '2020-03-12 15:06:15', 8, '_self');
INSERT INTO `permission` VALUES (134, '评论管理', '/admin/comment', 'menu', 0, 'fa fa-comment', 0, 'admin', '2020-03-07 19:46:32', 'admin', '2020-03-12 15:06:12', 11, '_self');
INSERT INTO `permission` VALUES (135, '回复我的', '/admin/comment/receive', 'menu', 134, 'fa fa-circle-o', 0, 'admin', '2020-03-07 19:57:53', 'admin', '2020-03-08 19:35:18', 20, '_self');
INSERT INTO `permission` VALUES (136, '我的评论', '/admin/comment/send', 'menu', 134, 'fa fa-circle-o', 0, 'admin', '2020-03-07 19:58:54', 'admin', '2020-03-12 15:13:32', 2, '_self');
INSERT INTO `permission` VALUES (137, '分类管理', '/admin/category', 'menu', 0, 'fa fa-book', 0, 'admin', '2020-03-07 20:00:57', 'admin', '2020-03-12 15:05:53', 6, '_self');
INSERT INTO `permission` VALUES (138, '分类列表', '/admin/category', 'menu', 137, 'fa fa-circle-o', 0, 'admin', '2020-03-07 20:01:36', 'admin', '2020-03-12 15:15:13', 1, '_self');
INSERT INTO `permission` VALUES (139, '新建分类', '/admin/category/new', 'menu', 137, 'fa fa-circle-o', 0, 'admin', '2020-03-07 20:02:14', 'admin', '2020-03-12 15:05:49', 1, '_self');
INSERT INTO `permission` VALUES (140, '删除文章', '/admin/post/delete', 'button', 133, '', 0, 'admin', '2020-03-08 15:02:20', 'admin', '2020-03-12 15:13:24', 1, '_self');
INSERT INTO `permission` VALUES (141, '批量删除文章', '/admin/post/batchDelete', 'button', 133, '', 0, 'admin', '2020-03-08 15:03:02', 'admin', '2020-03-12 15:13:17', 1, '_self');
INSERT INTO `permission` VALUES (142, '编辑文章', '/admin/post/edit', 'page', 133, '', 0, 'admin', '2020-03-08 15:03:49', 'admin', '2020-04-03 22:55:00', 1, '_self');
INSERT INTO `permission` VALUES (143, '保存文章', '/admin/post/save', 'button', 132, '', 0, 'admin', '2020-03-08 15:04:42', 'admin', '2020-03-12 15:13:15', 1, '_self');
INSERT INTO `permission` VALUES (144, '恢复文章', '/admin/post/revert', 'button', 133, '', 0, 'admin', '2020-03-08 15:05:23', 'admin', '2020-03-12 15:13:12', 1, '_self');
INSERT INTO `permission` VALUES (145, '移到回收站', '/admin/post/throw', 'button', 133, '', 0, 'admin', '2020-03-08 15:07:01', 'admin', '2020-03-08 15:07:01', 1, '_self');
INSERT INTO `permission` VALUES (146, '文件上传', '/admin/file/upload', 'button', 132, '', 0, 'admin', '2020-03-08 17:53:01', 'admin', '2020-03-08 17:53:01', 1, '_self');
INSERT INTO `permission` VALUES (147, '保存分类', '/admin/category/save', 'button', 139, '', 0, 'admin', '2020-03-08 18:51:48', 'admin', '2020-03-12 15:05:45', 1, '_self');
INSERT INTO `permission` VALUES (148, '编辑分类', '/admin/category/edit', 'button', 138, '', 0, 'admin', '2020-03-08 18:52:27', 'admin', '2020-03-12 15:05:40', 1, '_self');
INSERT INTO `permission` VALUES (149, '删除分类', '/admin/category/delete', 'button', 138, '', 0, 'admin', '2020-03-08 18:54:13', 'admin', '2020-03-12 15:13:40', 1, '_self');
INSERT INTO `permission` VALUES (150, '标签管理', '/admin/tag', 'menu', 0, 'fa fa-tag', 0, 'admin', '2020-03-08 19:19:59', 'admin', '2020-03-08 19:45:05', 8, '_self');
INSERT INTO `permission` VALUES (151, '标签列表', '/admin/tag', 'menu', 150, '', 0, 'admin', '2020-03-08 19:30:16', 'admin', '2020-03-08 19:30:16', 1, '_self');
INSERT INTO `permission` VALUES (152, '删除标签', '/admin/tag/delete', 'button', 151, '', 0, 'admin', '2020-03-08 19:32:56', 'admin', '2020-03-08 19:32:56', 1, '_self');
INSERT INTO `permission` VALUES (153, '所有评论', '/admin/comment', 'menu', 134, 'fa fa-circle-o', 0, 'admin', '2020-03-08 19:34:13', 'admin', '2020-03-13 11:56:36', 1, '_self');
INSERT INTO `permission` VALUES (154, '添加标签', '/admin/tag/new', 'menu', 150, '', 0, 'admin', '2020-03-08 19:42:31', 'admin', '2020-03-08 19:42:31', 1, '_self');
INSERT INTO `permission` VALUES (155, '编辑标签', '/admin/tag/edit', 'page', 151, '', 0, 'admin', '2020-03-08 19:43:06', 'admin', '2020-03-08 19:43:06', 1, '_self');
INSERT INTO `permission` VALUES (156, '保存标签', '/admin/tag/save', 'button', 154, '', 0, 'admin', '2020-03-08 19:44:45', 'admin', '2020-03-08 19:44:45', 1, '_self');
INSERT INTO `permission` VALUES (157, '删除评论', '/admin/comment/delete', 'button', 153, '', 0, 'admin', '2020-03-08 22:13:57', 'admin', '2020-03-12 15:15:26', 1, '_self');
INSERT INTO `permission` VALUES (158, '批量删除评论', '/admin/comment/batchDelete', 'button', 153, '', 0, 'admin', '2020-03-08 22:15:57', 'admin', '2020-03-12 15:15:35', 1, '_self');
INSERT INTO `permission` VALUES (159, '后台回复评论', '/admin/comment/reply', 'button', 153, '', 0, 'admin', '2020-03-08 22:27:39', 'admin', '2020-03-12 15:15:32', 1, '_self');
INSERT INTO `permission` VALUES (160, '置顶文章', '/admin/post/stick', 'button', 133, '', 0, 'admin', '2020-04-03 22:54:45', 'admin', '2020-04-03 22:54:45', 1, '_self');
INSERT INTO `permission` VALUES (161, '取消置顶文章', '/admin/post/unStick', 'button', 133, '', 0, 'admin', '2020-04-03 22:55:22', 'admin', '2020-04-03 22:57:07', 1, '_self');
INSERT INTO `permission` VALUES (162, '推荐文章', '/admin/post/recommend', 'button', 133, '', 0, 'admin', '2020-04-03 22:55:49', 'admin', '2020-04-03 22:55:49', 1, '_self');
INSERT INTO `permission` VALUES (163, '取消置顶文章', '/admin/post/unRecommend', 'button', 133, '', 0, 'admin', '2020-04-03 22:56:16', 'admin', '2020-04-03 22:56:16', 1, '_self');
COMMIT;

-- ----------------------------
-- Table structure for post
-- ----------------------------
DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `allow_comment` int(11) DEFAULT NULL,
  `post_content` longtext NOT NULL,
  `post_status` int(11) NOT NULL,
  `post_summary` varchar(2000) DEFAULT NULL,
  `post_thumbnail` varchar(255) DEFAULT NULL,
  `post_title` varchar(255) NOT NULL,
  `post_views` bigint(20) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  `comment_size` int(11) NOT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `post_likes` int(11) NOT NULL,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_by` varchar(255) DEFAULT 'admin',
  `update_by` varchar(255) DEFAULT 'admin',
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `post_type` varchar(10) NOT NULL,
  `is_recommend` int(1) NOT NULL DEFAULT '0',
  `is_sticky` int(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `FK5hvavpcp1efcf6vxo09bhi9a7` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of post
-- ----------------------------
BEGIN;
INSERT INTO `post` VALUES (14, NULL, '<h2>Kotlin，东宫太子</h2><p>谷歌今天宣布，Kotlin 编程语言现在是 Android 应用程序开发人员的首选语言。</p><p>谷歌在今天的声明中写道：&ldquo;Android 的开发将越来越以 Kotlin 为先。&rdquo;许多新的 Jetpack API 和特性将首先在 Kotlin 中提供。如果你要开始一个新项目，你应该用 Kotlin 来写；用 Kotlin 编写通常意味着更少的代码&mdash;&mdash;更少用于类型、测试和维护方面的代码。&rdquo;</p><p>就在两年前的 2017 年 I/O 大会上，谷歌宣布在其 Android Studio IDE 中支持 Kotlin。考虑到 Java 长期以来一直是 Android 应用程序开发的首选语言，这有点令人惊讶。在过去的两年里，Kotlin 的人气只增不减。谷歌说，超过 50% 的专业 Android 开发人员现在使用该语言开发他们的应用程序，在最新的 Stack Overflow 开发人员调查中，它被列为第四大最受欢迎的编程语言。</p><p>Android 的首席倡导者切特&middot;哈斯 (Chet Haase) 表示：&ldquo;我们宣布，我们正在采取的下一个重大举措是，我们将以 Kotlin 为先。&rdquo;</p><p>哈斯说：&ldquo;我们知道，不是每个人现在都在用 Kotlin，但是我们相信，你会需要它的。&rdquo;&ldquo;你可能有充分的理由继续使用 C++ 和 Java 编程语言，这完全没问题。这些语言不会消失。&rdquo;</p><p>Kotlin，Android 世界的 Swift？</p><p>早在 2015 年，Kotlin 就有&ldquo;Android 世界的 Swift &rdquo;的称号。</p><p>Kotlin 是一门与 Swift 类似的静态类型 JVM 语言，由 JetBrains 设计开发并开源。与 Java 相比，Kotlin 的语法更简洁、更具表达性，而且提供了更多的特性，比如，高阶函数、操作符重载、字符串模板。它与 Java 高度可互操作，可以同时用在一个项目中。</p><p>按照 JetBrains 的说法，根据他们多年的 Java 平台开发经验，他们认为 Java 编程语言有一定的局限性和问题，而且由于需要向后兼容，它们不可能或很难得到解决。因此，他们创建了 Kotlin 项目，主要目标是：</p><ul><li><p>创建一种兼容 Java 的语言</p></li><li><p>编译速度至少同 Java 一样快</p></li><li><p>比 Java 更安全</p></li><li><p>比 Java 更简洁</p></li><li><p>比最成熟的竞争者 Scala 还简单</p></li></ul><p>若在当时看来，Android 世界的 Swift 称号似乎底气不足，更像是一种美好的愿望。那么在 2017、2019 两届的 Google I/O 以后，这个说法可以站住脚了。</p><p>Kotlin 真比 Java 强？</p><p>许多新语言的出现似乎都是源于对某种其它语言的厌倦。似乎 Kotlin 也是如此。但在 JetBrains 看来，Kotlin 项目的原始动机就是为了提升生产力。JetBrains 开发者支持组组长 Hadi Hariri 在接受 InfoQ 采访时表示：</p><p><br></p><p>&rdquo;尽管当时我们已经开发了对几种基于 JVM 的编程语言的支持，我们还是基本都在 Java 环境下写基于 IntelliJ 的 API。IntelliJ 开发系统是基于 Groovy 和 Gant 的，Groovy 也用于测试，RubyMine 中还有一些 JRuby 代码，情况就是如此。我们希望转向更具表现力的语言从而提高生产力。同时，我们不能接受在 Java 互操作性或编译速度方面的妥协。&ldquo;</p><p><br></p><p>Kotlin 与 Java 总是在主观比较</p><p>&ldquo;Kotlin 比 Java 好&rdquo;，&ldquo;Kotlin 可读性比 Java 强&rdquo;，&ldquo;Kotlin 开发速度比 Java 快&rdquo;，类似这样的陈述缺少相关准确数据的支持，所以都归为主观看法一类。</p><p>主观看法是个体开发人员在对与 Kotlin 或 Java 相关的主题作出一个或多个主观判断时形成。</p><p>开发人员的主观判断存在如下问题：</p><ul><li><p>没有与主观判断相关联的量化指标。</p></li><li><p>主观判断存在很大的偏见。</p></li><li><p>主观判断的偏见在开发人员之间存在很大的差异。</p></li></ul><p>由于没有与主观判断相关联的量化指标，建立在这些判断基础上的观点只是反映出了开发人员之前就有的偏见。不同的开发人员可能有着截然不同的偏见，因此，有开发人员认为 Kotlin 是不错（或糟糕）的 Java 替代者并不意味着其他开发人员也这么认为。</p><p>而且，由于没有客观指标，主观分歧就无法客观地消除，这经常会导致&ldquo;口水战&rdquo;。</p><p>相较于 Java，Kotlin 的确在一些方面有较大优势：效率高、易维护、可靠、简单易学。在一些特定场景下，许多 Java 开发者因为某些方面的问题选择了切换到 Kotlin：比如受够了 Java NullPointerException 的人都喜欢 Kotlin 的 Null 安全特性；扩展函数被大量使用；除了扩展 Java 类，人们也常常将 Java 代码迁移到 Kotlin。</p><p>根据英国软件公司 Pusher 调查的数据显示，在样本数 2744 人的调查中，超过 87% 的受调者已经完成了迁移。他们有的使用了迁移向导，有的直接手动修改代码。超过四分之一的受调者迁移到 Kotlin 后又回到了 Java。有技术方面的原因，也有组织方面的原因。其中使用了反射或代码生成的工具是被提及最多的因素。</p><ul><li><p>&ldquo;Kotlin 的枚举不能包含常量。在自定义注解时（比如 @IntDef），为了保持接口的整洁，需要将值保存在枚举中。&rdquo;</p></li><li><p>&ldquo;我们正在使用 Realm，但它不能与数据类一起使用。&rdquo;</p></li><li><p>&ldquo;我们的 Java 代码中使用了 Retrolambda，因为类型缺失，很难转到 Kotlin。&rdquo;</p></li><li><p>&ldquo;另一个团队不喜欢 Kotlin，我们也预料不到会这样。&rdquo;</p></li><li><p>&ldquo;这不是我们决定的，我们是按照公司的规则来的。&rdquo;</p></li></ul><p>Java 依旧是编程语言排行榜上的第一名。但 Java 是最好的语言么？不是，因为在每个领域都有更合适的编程语言。</p><p>那么，Java 语言到底有什么优势可以占据排行榜第一的位置呢？</p><ul><li><p>其一，语法比较简单，学过计算机编程的开发者都能快速上手。</p></li><li><p>其二，在若干了领域都有很强的竞争力，比如服务端编程，高性能网络程序，企业软件事务处理，分布式计算，Android 移动终端应用开发等等。</p></li><li><p>最重要的一点是符合工程学的需求，成为企业软件公司的首选，也受到互联网公司的青睐。</p></li></ul><p>综合而言，Java 语言全能方面是最好的。但同样可以看到，Android 社区拥抱 Kotlin 的速度越来越快，也许有一天，在 Android 世界里，我们会看到 Kotlin 对 Java 的超越。</p><h2><s>为什么谷歌会支持 Kotlin？</s></h2><p><br></p><p>免责声明：以下内容纯属基于既有事实合理推测，毫无任何实锤。</p><p><br></p><p>还记得谷歌与 Oracle 旷日持久的 Java 侵权案吗？这个持续时间长达 8、9 年的纷争目前以谷歌败诉为最新结局，其需要向 Oracle 赔偿 88 亿美元。（编者注：在过去几年时间里，几次裁决分别判两家公司胜诉或败诉。今年 1 月份，谷歌不服判决上诉美国最高法院做最终裁决。）</p><p>事件的起因是在 2010 年，作为 Java 拥有者的 Oracle 认为，谷歌在 Android 系统上无偿使用了 37 个 Java APIs，这侵犯了他们的专利，而在 Android 中还有 9 行代码抄袭了 Java，这侵犯了他们的版权。</p><p>那 9 行代码造成抄袭的缘由据说是因为当时谷歌的一位工程师在为 Android 项目工作的同时，又为 Sun 公司的 OpenJDK 效力，后来，该工程师直接从 OpenJDK 中复制了 9 行代码到 Android 中，由于 Google 没有得到 Sun 公司的授权，所以 Oracle 收购 Sun 之后，Java 易主，这就相当于侵犯了 Oracle 的版权。</p><p>而对于那 37 个 Java APIs，双方各执己见，谷歌认为 API 不应受版权保护，而 Oracle 则认为 谷歌对其的使用具有很强的商业性，再加上用 Java 写的 Android 代码无法在 JVM 上运行，这对 Java 语言是一种分裂，同时 Oracle 认为，Android 通过不当使用 Java API 挤占了 Java ME 可能的市场，这是它巨额索赔的根基。</p><p>2016 年初，Google 发言人表示，Android N（7.0) 不再沿用现有的 Java APIs 内容，而是迁移至基于 OpenJDK 的方法，为开发者创建一个通用代码库。虽然没法规避 Java APIs 官司，但至少可以减少潜在的可能纠纷。</p><p>到了 2017 年的 Google I/O 大会，Google 宣布 Kotlin 成为 Android 开发的一级语言，可以说对 Kotlin 寄予了厚望。此后，Kotlin 也被开发者视为 Java 的替代品。</p><p>在之后的一年时间里，Google 可以说把 Kotlin 当亲儿子看待，为什么这么说呢？让我们来梳理这一年 Google 为 Kotlin 做了什么：</p><ul><li><p>2017 年 11 月，Android Studio 3.0 正式开放下载，此版本的 Android Studio 将 Kotlin 语言支持集成到 IDE 中，在此版本上，代码自动完成和语法突出显示都可以在此版本上平稳运行，今年 4 月推出的 Android Studio 3.1 为 Kotlin 代码提供了更好的 Lint 支持，并通过为 Android Emulator 添加 Quick Boot 功能加快了测试速度。</p></li><li><p>2018 年 2 月，Google 推出预览版本的 Android KTX，Android KTX 是一组扩展程序，它在 Android 框架和支持库上提供了一个良好的 API 层，使 Kotlin 代码更加简洁。</p></li><li><p>在 Google I/O 2018 上，Google 发布了 Android Jetpack，它是下一代的 Android 组件，它将支持库向后兼容和立即更新的优点融合到更多组件中，提高开发速率和质量，不仅如此，Android Jetpack 将全面兼容 Kotlin，而且它还能利用 Android KTX 使得 Kotlin 代码更加简洁。</p></li></ul><p>这些都是 Google 逐渐向 Kotlin 靠拢的证据，虽然还不至于让 Kotlin 完全取代 Java，但不难看出 Google 的&ldquo;偏心&rdquo;。事实上，Kotlin 自己也非常争气：2017 年 11 月，在第一届 Kotlin 专题大会 KotlinConf 上，Kotlin 首席设计师 Andrey Breslav 宣布 Kotlin 将支持 iOS 开发和 Web 开发，这意味着 Kotlin 向全平台开发迈出了重要的一步。</p><p>Kotlin 目前正处于发展的初始阶段，还有很多成长的空间。Google 现在是把它当成 Android 黄昏时期的救命稻草，它能与 Java 100% 互通，但它存在的目的并不是为了取代 Java，只是为了让开发者有多种选择。虽说编程语言只是软件实现的一种工具，开发者无论选择哪种语言都没有绝对的对与错。但在互联网时代，开发者应该懂得审时度势，拥抱变化，才能走得更远。</p>', 0, 'Kotlin，东宫太子谷歌今天宣布，Kotlin 编程语言现在是 Android 应用程序开发人员的首选语言。谷歌在今天的声明中写道：&ldquo;Android 的开发将越来越以 Kotlin 为先', NULL, 'Java 失宠，谷歌宣布 Kotlin 现在是 Android 开发的首选语言', 3, 1, 2, '2020-03-12 18:10:56', 0, '2020-03-12 18:44:08', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (15, NULL, '<h2>你的面孔，他的&ldquo;玩物&rdquo;</h2><p>&ldquo;上流社会&rdquo;人士从来不缺乏对于新技术的&ldquo;热情&rdquo;。</p><p>2018 年 10 月的一个星期二晚上，Gristedes 便利连锁店的亿万富翁老板 John Catsimatidis 在曼哈顿苏活区附近一家高档意大利餐厅用餐，他的女儿 Andrea 与一名 Catsimatidis 不认识的人一同走了进来。显然，他的女儿没有发现自己的父亲也在这家餐厅里，此时，Catsimatidis 叫服务员走过去拍张照片。</p><p>随后，Catsimatidis 将图片上传到手机上的面部识别应用程序&mdash;&mdash;Clearview AI。该应用程序背后的初创公司拥有一个庞大的数据库，其中包含数十亿张照片，这些照片是从 Facebook、Twitter 和 LinkedIn 等网站上抓取的。几秒钟之内，Catsimatidis 看到了这个神秘男子的照片集，以及它们出现的网址，并且清楚地知道了：他女儿的约会对象是来自旧金山的一位风险投资家。</p><p>Catsimatidis 说：&ldquo;我只是想确保他不是骗子。&rdquo;随后，他将该男子的简历发给了女儿。</p><p>Andrea 的约会对象惊讶于 Catsimatidis 如何迅速地得到这样详尽的信息。而 Andrea 则表示：&ldquo;我了解我父亲，他确实能够做出这样疯狂的事情，他对新的技术非常敏感。&rdquo;</p><p>Catsimatidis 是从公司另一位创始人那里知道了这一应用，而在此前，那位创始人已经把这一应用放在了部分线下商店里，用以侦察小偷或竞争对手的潜入。除此之外，Clearview AI 早已在美国&ldquo;上流社会&rdquo;的聚会中流传开来，他们用这一软件来识别感兴趣的陌生人，或者帮他们想起那些熟悉但不记得名字的脸孔。</p><p>至于 Clearview AI 本身也是很乐于向这些人提供服务的，&ldquo;我们向潜在和现有投资者以及其他战略合作伙伴提供了试用帐户，以便他们可以测试该技术。&rdquo;该公司的联合创始人 Hoan Ton-That 说。</p><p>&ldquo;人脸搜索&rdquo;产业浮出水面</p><p>一张照片，只消数分钟，一个人在全网发布过的照片以及来源就查的一清二楚，这样的操作让人不寒而栗，而其背后的&ldquo;人脸搜索&rdquo;产业更加让人惶恐。</p><p>以 Clearview AI 为例，这家公司虽然以人脸识别技术起家，但是在业内也算得上是&ldquo;名声在外&rdquo;了。</p><p>这家公司声称自己设计了一套人脸识别寻人系统：用户可以通过上传某人的照片到该系统，即可获得此人在全网公开的照片信息及其源头链接，简单来讲就是&ldquo;一张照片，全网寻人&rdquo;。同时，该公司表示，他们从 Facebook，YouTube，Venmo 等数百万其他网站中抓取了约 30 亿张图像，远远超出了美国政府或硅谷科技巨头们建造的任何数据库。</p><p>该公司创始人 Hoan Ton-Town 说，他希望将这套系统提供给执法部门使用，这样可以大大增加走失人员的搜索以及暴恐人员的跟踪力度。比如，Clearview AI 就曾声称协助纽约警方破获了一起疑似爆炸物的案件，虽然纽约警方后来表示案件破获与这家公司并无关系。</p><p><img src=\"https://constatic.geekbang.org/infoq/5e6717a2bde96.png?imageView2/0/w/800\" alt=\"人脸搜索，在AI的阴暗面肆无忌惮\" class=\"fr-fic fr-dii\"></p><p>Clearview AI 号称协助破案的宣传文件</p><p><br></p><p>抛开这一&ldquo;打脸&rdquo;事件不说，像 Clearview AI 这样依靠&ldquo;人脸搜索&rdquo;业务吃饭的公司正在悄悄崛起，甚至在逐渐成长为一个庞大的产业。</p><p>比如，一家名叫 Trustwave 的新加坡公司就推出了一款基于人脸识别的情报搜索工具&mdash;&mdash;SocialMapper。使用方法同样简单，只需要上传一张人脸照片和姓名，就能找到此人在 Twitter、Facebook、LinkedIn、Instagram，甚至微博、豆瓣等等社交媒体上的用户主页。最后，SocialMapper 还会综合这些社交媒体内容出具报告，报告会包含性别、年龄、所在地、电子邮箱等等详细的个人信息。</p><p>除了形形色色的 App，还有一些专门以搜脸找人为核心业务的网站。这些网站往往打着一些看似正义的旗号，比如&ldquo;帮您查看是否有人非法使用您的照片&rdquo;，实际上则是依靠人脸搜索来帮助别有用心之人完成类似人肉搜索的业务。这些网站甚至会对不同的业务明码标价，按级别收费，某些网站的&ldquo;高级 VIP&rdquo;甚至可以获得极其详细的目标人物资料，与之相关的亲属等信息也会统统暴露。</p><p><img src=\"https://constatic.geekbang.org/infoq/5e6717a282e24.png?imageView2/0/w/800\" alt=\"人脸搜索，在AI的阴暗面肆无忌惮\" class=\"fr-fic fr-dii\"></p><p>某人脸搜索宣传</p><p><br></p><h2>在质疑声中成长的人脸识别</h2><p>2020 年 1 月，Twitter 向 Clearview AI 发送了一封停止访问信，指出该公司从其网站收集了照片，这违反了 Twitter 的服务条款，并要求 Clearview AI 停止数据抓取并删除从 Twitter 收集的所有数据；不久后，Facebook 也对 Clearview AI 发出了类似的警告。</p><p>虽然该公司一再强调，其技术&ldquo;仅适用于执法机构和选择安全专业人员作为调查工具&rdquo;。但是也并没有任何行动来制止这项技术被滥用。或许是无能为力，又或者是收到了&ldquo;其他力量&rdquo;的控制，总之 Clearview AI 要面临的问题恐怕不止一两件，据悉，美国伊利诺斯州东部北区地方法院已经向 Clearview AI 提起诉讼，称 Clearview AI 的行为是对公民自由的威胁。</p><p>Clearview AI 未来的命运会如何暂且不表，回过头来说说人脸识别这项质疑声中不断成长的技术。</p><p>安全问题是一直围绕着人脸识别技术的核心话题，虽然无数企业都在强调对于这项技术安全性的保障，并且在很多场景下，人脸识别真的提供了不少的便利，但是质疑一直没能从用户的心头打消。</p><p>相比搜脸寻人这种刚刚崛起的产业，Deepfake、AI 换脸这样的名词或许更能引起人们的警觉。毕竟当换脸技术刚刚兴起的时候，就在全球引发了热议，换脸 App 虽然挑起了一段时间的热度，但更加剧了人们对于人脸识别的恐惧&mdash;&mdash;换脸之后毫无违和感，不正可以用来造假吗？</p><p>受舆论的压力影响，一些换脸 App 下架了，就连 GitHub 也禁封了 Deepfake 的相关开源项目。但是影响已经形成，且仍然有不少人在早先下载好的代码中不断&ldquo;优化&rdquo;，在那些普通用户看不到的地方继续散播着伪造的图片、视频。</p><p>除了来自用户的不信任，人脸识别还被不同国家和地区的立法机构&ldquo;特殊关注&rdquo;，比如第一个全面禁止人脸识别的城市&mdash;&mdash;旧金山，以及号称&ldquo;史上最严&rdquo;的 GDPR 都曾对人脸识别的&ldquo;不正确使用&rdquo;进行过处罚或警告。</p><p>虽然这些严格执行的法律一定程度上保护了用户的隐私安全，但却也不同程度地限制了技术的发展。安全与发展两者如何能全面发展，也成为了立法者与企业不断为之努力的方向。</p><h2>结 语</h2><p>人脸识别，老生常谈，常谈常新。</p><p>技术在进步，制度在完善，但是灰色、黑色的产业链却总也没法一网打尽，本该用来向善的科技也在那些阴暗的角落里滋生更多的病毒，甚至成为某些权钱交易的&ldquo;桥梁&rdquo;。是的，技术向善在与人，但人心若向恶又当如何？</p><p>拓展阅读：</p><p>https://www.infoq.cn/article/422lAN7NjQVkRYuZ8vcs</p><p>https://www.nytimes.com/2020/03/05/technology/clearview-investors.html</p>', 0, '你的面孔，他的&ldquo;玩物&rdquo;&ldquo;上流社会&rdquo;人士从来不缺乏对于新技术的&ldquo;热情&rdquo;。2018 年 10 月的一个星期二晚上，Gristedes', NULL, '人脸搜索，在 AI 的阴暗面肆无忌惮', 2, 1, 0, '2020-03-12 18:12:21', 0, '2020-03-12 18:12:43', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (16, NULL, '<p>&ldquo;去 O&rdquo;一直是最近 10 年描述系统架构改造中最常出现的词之一。虽然&ldquo;去 O&rdquo;被很多工程师和技术从业者津津乐道，但业界真正能实现把系统全部去 O，特别是金融场景的核心系统全部去 O 的案例并不多。那么去 O 到底难在哪里呢。</p><p>为了解答这个问题，首先我们要理解去 O 架构改造的本质是什么？去 O 架构改造的本质其一是让系统架构具备在线更换数据库的能力，无论去 O 的目标库是 MySQL，或是其他的关系型数据库，最终都是要解决这样一个问题。</p><p>在线更换数据库到底难在哪里，会遇到哪些问题呢？</p><p>问题一：如何无感知的实时动态数据的迁移？</p><p>首先数据库作为交易型系统最核心的组件没有之一，这一点对于数据库的重要性评价一点都不夸张。当前大部分知名的网站和系统都是 7x24 小时对外提供服务，数据库也是 7*24 小时无时不刻处理着大量的读写服务，要实现去 O 就意味着你要在一个 Oracle 库还在对外提供服务的时候，在某个时间点让一个 MySQL 库快速替换掉 Oracle 库，并平稳的接管 Oracle 的所有服务。</p><p>不同于无状态的系统组件切换把流量切走即完成切换工作，而数据库作为有状态的系统组件，如何设计一套从应用改造、到数据同步、再到数据库流量切换的稳妥去 O 方案，可以非常谨慎的把一个正在对外提供服务，数据处在实时变化状态的 Oracle 库上的数据无缝的方式迁移至 MySQL 中。</p><p>为了有效解决这个问题，陆金所研发的去 O 工具包含一整套完善的在线数据迁移功能。在工具中勾选需要去 O 的 Oracle 表，工具会自动完成 O to M 的全量同步、增量同步，并通过解析 Oracle redolog 来追增量日志，最终形成一个 Oracle 为主库，MySQL 为备库的异构实时备库。</p><p>问题二：如何管理和协调好高频迭代的去 O 改造和功能改造？</p><p>其次去 O 架构改造的主体工作是对应用层代码的重构，特别对 DAO（数据访问层）的重构，对于某些复杂的系统来说，重构的时间会持续数月甚至更久。在这段漫长的去 O 改造时间窗口里，不但 Oracle 库的数据在动态发生变化，对于一个处在高速迭代中的网站和系统来说，应用的功能代码也在不断发生变化。如果 A 团队在为应用做去 O 架构改造，而这个期间 B 团队在不断的给应用开发新的功能，如何进行去 O 的工作拆分可以有效的管理和协调好两个开发团队的编码和上线节奏呢。</p><p>为了有效应对这个场景，陆金所研发的去 O 工具会在去 O 架构改造和应用业务改造之前进行有效协调，并向业务开发尽可能屏蔽去 O 架构改造的影响。比如业务改造需要在处于 O 和 M 并行双写的库上修改表结构并发布新的数据库访问接口，大量的工作会由去 O 工具来自动化完成。</p><p><img src=\"https://static001.infoq.cn/resource/image/39/7a/39c99b11fc68e8b8ea609136066a807a.png\" alt=\"金融系统去Oracle实践，到底需要解决哪些问题？\" class=\"fr-fic fr-dii\"></p><p>问题三：如何稳妥落地数据库流量的在线切换？</p><p>当某个库的应用去 O 改造完成并上线后，会实施生产环境 Oracle 的流量切换到 MySQL 上。在这个切换过程中，如何确保 Oracle 上的最后一笔事务提交成功，并同步到 MySQL 后完成数据一致性校验，且针对这个 Oracle 库的所有写操作能在快速、全部切换到 MySQL 上，不会出现部分写流量 Oracle，部分写流量 MySQL，两库双写的异常状态。</p><p>当流量切换到 MySQL 后 a，如果出现应用报错或 bug、MySQL 性能问题等在前期压测或准备工作中未覆盖到的突发情况，如何实现流量快速回切到 Oracle 上，且确保在 MySQL 中写入的数据也能完全一致的回到 Oracle 中。</p><p>解决好这个问题，是控制好去 O 落地风险的核心所在。陆金所设计了一整套在线切换数据库的架构框架来确保在瞬间把流量从 Oracle 切走到 MySQL 中，同时也可以瞬间把流量切回到 Oracle，并确保两边的数据完全一致。</p><p><img src=\"https://static001.infoq.cn/resource/image/cb/79/cb77e1221fa15a28a0967a50ec500379.png\" alt=\"金融系统去Oracle实践，到底需要解决哪些问题？\" class=\"fr-fic fr-dii\"></p><p>问题四：如何有效拆分去 O 的任务，从而实现对全站业务单次影响小、迭代频度快的去 O 上线？</p><p>要实现全站去 O，必然面临着对一些复杂、庞大的核心系统进行去 O 改造。以陆金所为例，在全站中像用户中心、资产中心、资金账户等这种给全站所有金融产品线都提供基础服务的子系统就是这类复杂和庞大的核心系统，同时包括基金、主账户等专属金融产品线的业务逻辑复杂，所以子系统也非常庞大。</p><p>所以对于这类子系统，如果需要在一个大版本里全部去 O 改造完成，并在一个晚上业务低峰期一次性全部从 O 切换到 M，无论是当晚的切换风险，还是切换完成后，在第二天业务高峰期出现问题和 bug 的风险，包括开发团队短时间内去 O 改造的工作量和出现重大 bug 的机率都是非常大且不可控的。</p><p>如何把一个庞大且重要的复杂子系统拆分成多个去 O 的版本按批次上线和切换流量，且做到单个批次影响可控，也是全站去 O 中需要谨慎设计的方案。</p><p>而这也是整个去 O 过程中架构设计最有趣的部分。</p><p>上面提到了去 O 中在架构层实现在线换库需要解决的四大问题。除了在线换库外，去 O 架构改造的本质其二是引入更多的存储引擎在合适的场景来承接 Oracle 数据库的计算和存储能力。这就引出了第五个问题。</p><p>问题五：如何在各种场景下使用合适的开源存储引擎来去 O，并且在架构上进行融合。</p><p>首先 Oracle 是个非常强大的关系型数据库，无论在 OLTP 和 OLAP 场景表现都很出色，且具备一整套完善、好用的运维和监控工具。但于此同时 Oracle 虽然对各种场景支持较为全面，但在各个特定场景下，一些开源的数据库或存储引擎在性能或成本投入的综合考量上胜过 Oracle，都会是比 Oracle 更合适的选择方案。</p><p>所以全站去 O 不仅仅是去 O 到 MySQL 中，MySQL 能承接的只是 Oracle 的部分计算和存储能力，在整个陆金所的全站去 O 落地过程中，除了 MySQL 外，我们还在不同的场景下使用 ES、HBase、TiDB、Impala+kudu 等存储引擎，甚至是应用层的代码来承接和替换 Oracle，并且整体收益比使用 Oracle 更好。</p><p>在完成去 O 后，陆金所的生产环境出现了大量开源的存储引擎来支撑各种合适的业务场景。同时我们也研发了数据总线平台来实现数据在一个地方写入和提交，秒级同步到其他存储引擎的架构。</p><p><img src=\"https://static001.infoq.cn/resource/image/8a/8e/8a4b88fcfeab8fa62c6c6b5a6617418e.png\" alt=\"金融系统去Oracle实践，到底需要解决哪些问题？\" class=\"fr-fic fr-dii\"></p><p>上述是陆金所在全站去 O 过程中遇到的 5 个实战问题大类，整个全站去 O 过程中需要解决细节问题还有很多，这里无法一一列举，因为去 O 作为一个复杂的系统架构改造本身就要求技术团队事无巨细的处理好各种细节问题。</p><p>基于此，陆金所优化和开发了一整套方案和工具来，有效推进去 O 改造稳妥落地且保障风险可控。后续会推出一个系列的去 O 专题和大家分享，希望给有去 O 改造计划的技术团队和公司带来一些参考和借鉴价值，敬请期待。</p>', 0, '&ldquo;去 O&rdquo;一直是最近 10 年描述系统架构改造中最常出现的词之一。虽然&ldquo;去 O&rdquo;被很多工程师和技术从业者津津乐道，但业界真正能实现把系统全部去 O，特别', NULL, '金融系统去 Oracle 实践，到底需要解决哪些问题？', 0, 3, 0, '2020-03-12 18:13:52', 0, '2020-03-12 18:13:52', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (17, NULL, '<p>前几天，我在 Reddit 上面闲逛的时候，发现了一篇有趣的文章，名为《影响我们世界的十大算法》。作者 George Dvorsky 希望通过此文解释算法在当今世界上的重要意义，以及哪些算法为我们的文明做出突出贡献。</p><p>现在，如果大家对于算法有些涉猎，那么在读过文章后的第一个想法很可能是&mdash;&mdash;作者真的知道算法是什么吗？或者说 Facebook 新闻源是否属于算法？因为如果 Facebook 新闻源也是一种算法，那么我们几乎可以把一切东西都归结为算法。因此，我希望在本篇文章中解释算法的真实定义，而后探讨 10 种（或者更多）真正支配着整个世界的算法。</p><p>算法究竟是什么？</p><p><br></p><p>直白地讲，算法是指一切经过明确定义的计算过程，其将某个或者某组值作为输入内容，并产生某个或者某组值作为输出结果。因此，算法代表的是一系列计算步骤，用于将输入转换为输出。&mdash;&mdash;资源来源：Thomas H. Cormen 与 Chales E. Leiserson (2009 年)，《算法导论》第 3 版。</p><p><br></p><p>更简单地总结，我们可以将算法视为一系列用于解决某个任务的步骤（是的，不仅仅是计算机会使用算法，人类同样在使用算法）。就目前的标准来看，算法应当具有以下三大重要特征才被视为拥有实际效果：</p><ol><li><p>应该是有限的: 算法应该在有限的时间内用有限的步骤解决掉其旨在解决的问题，也就是说算法必须在有限的时间内可以完成，要不然就没有现实意义。</p></li><li><p>应该具有明确的指令: 算法中的每个步骤必须经过精确定义 ; 同时应针对每种情况做出明确说明。</p></li><li><p>应该切实有效: 算法应当能够解决其旨在解决的问题。此外，算法应该被证明可以单纯利用纸笔工具实现收敛。</p></li></ol><p>此外，需要强调的是算法的应用不仅局限于计算科学，同时它也作为一种数学实体。事实上，早在公元前 1600 年就已经出现第一条记录在案的数学算法&mdash;&mdash;巴比伦人发现了最早的已知算法，用于分解平方根。因此，回到文章开头我们讨论的问题，我读到的那篇文章将算法视为计算实体，但如果采取这样一个更为宽泛的定义，那么支配世界的十大算法很可能体现为算术方法（例如减法、乘法等）。</p><p>但是，如果采取我们在本文中做出的算法定义，那么问题仍然存在：支配世界的十种算法究竟有哪些？在这里，我列出一份小小的清单，排名不分先后。</p><p>1. 合并排序，快速排序与堆排序</p><p><img src=\"https://static001.geekbang.org/wechat/images/28/28f8f7971b1f226775a563ddb63184ed.png\" alt=\"真正支配世界的十种算法\" class=\"fr-fic fr-dii\"></p><p>对元素进行排序的最佳算法是什么？具体答案取决于你的实际需要，因此我把这三种比较常用的排序算法列为同一类 ; 也许你更偏爱其中一种，但事实上三者都非常重要。</p><p>其中合并排序算法是迄今为止我们所拥有的最为重要的算法之一。这是一种基于比较的排序算法，以分治的方法解决原本时间复杂度为 O(n^2) 的问题。该算法由数学家 John von Neumann 于 1945 年发明得出。</p><p>快速排序是另一种用于解决排序问题的方法，其能够实现就地分区，同样属于一类分而治之的算法。该算法的问题在于其在排序方面并不稳定，但在对基于内存的数组进行排序时表现出色。</p><p>最后是堆排序算法，其利用优先级队列来减少数据中的搜索时间。该算法同样属于就地算法，且同样不属于稳定排序。</p><p>这些算法相较于我们之前使用过的其它方法（例如冒泡排序）有了很大的改进。事实上，正是由于这些算法的出现，我们才得以迎来数据挖掘、人工智能等网络上常见的众多现代计算工具。</p><p>2. 傅利叶变换与快速傅利叶变换</p><p><img src=\"https://static001.geekbang.org/wechat/images/46/467017bc978831b23a99833f2c2d3872.jpeg\" alt=\"真正支配世界的十种算法\" class=\"fr-fic fr-dii\"></p><p>整个数字世界都在使用这些简单但非常强大的算法，这些算法能够将信号从时域转换为频域，反之亦然。事实上，正是由于这些算法的存在，本篇文章才能被更多朋友所看到。</p><p>互联网、Wi-Fi、智能手机、电话、计算机、路由器以及卫星等几乎一切具有内置计算装置的设备都会以这样或者那样的方式使用这些算法以保持运行。如果不研究这些重要的算法，大家也不可能拿下电子学、计算机或者通信科学学位。</p><p>3. 迪杰斯特拉算法（Dijkstra，又译戴克斯特拉算法）</p><p><img src=\"https://static001.geekbang.org/wechat/images/8a/8a6e051c7cf08e18f9c537126f4fc1be.jpeg\" alt=\"真正支配世界的十种算法\" class=\"fr-fic fr-dii\"></p><p>实事求是地讲，如果没有这种算法，互联网根本无法像今天这样保持高效运作。这种图搜索算法具有多种应用方式，能够将需要解决的问题建模为图，并在其中找到两个节点间的最短路径。</p><p>今天，虽然我们已经拥有更好的最短路径问题解决方案，但迪杰斯特拉算法仍然在强调稳定性的众多系统当中得到广泛应用。</p><p>4. RSA 算法</p><p>如果没有加密与网络安全机制作为保障，互联网的重要程度不可能达到如今的水平。大家可能会想&ldquo;胡说，国家安全局局和众多情报机构的监控早就毁掉了互联网安全&rdquo;或者&ldquo;互联网根本就没有安全可言，傻子才会相信这种安全宣传&rdquo;; 但必须承认，大多数人仍然具有一定程度的安全信心，否则你根本就不会通过互联网进行消费。毕竟如果真的否定现有网络体系的安全性，谁会愿意在 Web 服务中输入自己的信用卡号码？</p><p>在密码学领域，有一种算法仍然是目前世界上最重要的算法之一，这就是 RSA 算法。该算法由 RSA 公司的创始人们开发而成，使得密码学成果得以供世界上的每个人随意使用，甚至最终塑造了当今密码学技术的实现方式。RSA 算法希望解决的问题是如何在独立平台及最终用户之间共享公钥，从而实现加密（当然，我认为 RSA 算法并没能彻底解决这个问题，从业者们还需要在这个方向上投入更多努力）。</p><p>5. 安全哈希算法</p><p>这实际上并不是真正的算法，而是由 NIST（美国国家标准技术研究所）所开发的一系列加密散列函数。然而，该算法家族对于世界秩序的维持起到了至关重要的作用。从应用程序商店、电子邮件、防病毒软件再到常用的网络浏览器，这一切都在使用这类算法（实际上，使用的是由这类算法生成的哈希值），用以确定你所下载的是否正是你希望获得的内容，或者你是否已经成为中间人攻击或者网络钓鱼攻击的受害者。</p><p>6. 整数分解</p><p>这是一种在计算领域被大量采用的数学算法。如果没有这种算法，密码学技术的安全水平将受到严重破坏。该算法用于将复合数的质数因子分解为较小的非零因数。这也被称为 FNP 类问题，属于 NP 类问题的扩展，且解决难度极高。</p><p>很多加密协议都以分解大型复合整数或相关问题的难度为基础&mdash;&mdash;RSA 算法就是其中的典型代表。正是由于对任意整数进行因子分解的难度极高，才使得基于 RSA 的公钥加密机制拥有较高的安全性水平。</p><p>量子计算的诞生大大降低了此类问题的解决难度，并开辟出一个全新的科学研究领域&mdash;&mdash;利用量子特性保障系统安全。</p><p>7. 链接分析</p><p><img src=\"https://static001.geekbang.org/wechat/images/60/607519b2cd07fb2322ba95028d7800c9.jpeg\" alt=\"真正支配世界的十种算法\" class=\"fr-fic fr-dii\"></p><p>在互联网时代下，分析不同实体间的关系当然非常重要。从搜索引擎到社交网络再到营销分析工具，每一方都在努力发现随着时间推移而不断变化的互联网结构。</p><p>链接分析可以说是普罗大众眼中最神秘也最难以理解的算法之一。问题在于，我们可以通过多种不同方法实现链接分析，而且多种特征的存在使得每种算法间都存在着一定差异（允许对算法申请专利），但其底层基础却又高度相似。</p><p>链接分析背后的基本思路非常简单，即允许使用者以矩阵的形式表示图形，从而将其转化为特征值问题。这一特征值可以为我们提供衡量图形结构以及各节点相对重要性的好方法。该算法由 Gabriel Pinski 与 Francis Narin 于 1976 年发明得出。</p><p>那么，谁在使用这一算法？谷歌公司需要进行网页排名，Facebook 需要发布新闻提要（因此，我们将 Facebook 的新闻源服务视为算法结果，而非算法本身），Google+ 与 Facebook 的好友推荐，LinkedIn 的工作岗位与联系人推荐，Netflix 与 Hulu 的影视关联、YouTube 的相关视频等等皆属于这一类。虽然其各自拥有不同的目标与参数组合，但背后的数学原理却是相通的。</p><p>最后，我想强调一点，虽然很多人认为谷歌公司似乎是第一家使用这种算法的企业，但早在 1996 年（谷歌公司诞生的两年之前），由 Robin Li 开发的 RankDex 小型搜索引擎已经开始利用这一基本思路进行页面排名。最终，HyperSearch 的创始人 Massimo Marchiori 也开始使用这种基于单页间关系的页面排名算法。（谷歌在其申请的专利当中提到了这两位奠基者。）</p><p>8. 比例微积分算法</p><p><img src=\"https://static001.geekbang.org/wechat/images/a1/a182389909a26e8162b1344fed251c3f.png\" alt=\"真正支配世界的十种算法\" class=\"fr-fic fr-dii\"></p><p>大家应该都体验过飞机、汽车、卫星服务或者手机网络吧？有些朋友还在工厂当中看到过机器人设备。如果是这样，那么你已经见识到了这一算法的威力。</p><p>该算法旨在利用控制回路反馈机制以最大程度控制期望输出信号与实际输出信号间的误差。其适用于一切存在信号处理需求的场景，包括以自动化方式通过电子技术控制的机械、液压或者热力系统。</p><p>也可以说，如果没有这种算法，那么我们的现代文明将无从谈起。</p><p>9. 数据压缩算法</p><p>很难确定哪种压缩算法的重要性最高，因为根据实际应用需求，大家使用的算法可能包括 zip、mp3 乃至 JPEG 以及 MPEG-2 等等。但相信大家都能清晰地感受到这些算法在各类结构中的重要作用。</p><p>除了最直观的文件压缩之外，大家还能在哪里看到压缩算法的踪影？很明显，网页会利用数据压缩技术控制你需要下载的文件体积，此外视频游戏、视频、音乐、数据存储、云计算以及数据库等也都是数据压缩算法大显身手的舞台。可以说，万事万物都离不开数据压缩，这类算法的存在使得系统能够以成本更低且效率更高的方式为用户服务。</p><p>10. 随机数生成算法</p><p><img src=\"https://static001.geekbang.org/wechat/images/dc/dc6bb6867496c7050a38842e37e306c7.png\" alt=\"真正支配世界的十种算法\" class=\"fr-fic fr-dii\"></p><p>今天，我们还没有&ldquo;真正的&rdquo;随机数生成器，但已经拥有众多完全可以满足需求的伪随机数生成器。这些算法广泛存在于互连链接、加密、安全哈希算法、视频游戏、人工智能、优化、问题条件初始化以及财务等领域。</p><p>最后，我想补充一点：这份清单只代表一种观点，而非真正全面的列表。因为在机器学习、矩阵乘法以及分类等领域还存在着诸多堪称文明社会根基的重要算法，而我在本文中并没有明确提及。</p>', 0, '前几天，我在 Reddit 上面闲逛的时候，发现了一篇有趣的文章，名为《影响我们世界的十大算法》。作者 George Dvorsky 希望通过此文解释算法在当今世界上的重要意义，以及哪些算法为我们的文', NULL, '真正支配世界的十种算法', 1, 1, 0, '2020-03-12 18:15:41', 0, '2020-03-12 18:25:48', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (18, NULL, '<p><br></p><p><img src=\"https://static001.infoq.cn/resource/image/5b/50/5bbc22ba8f487342066a19210f02e050.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><p>为提高代码的质量、安全性和可维护性，软件工程师每天会用到无数工具。</p><p>我会列出一些自己最喜欢的 python 工具，并从易用性（是否易于安装、运行和自动化）、质量影响（能否阻止可预见的 bug）、可维护性影响（是否让工作更轻松）和安全性影响（能否发现并阻止安全性问题）对它们进行打分，以供读者参考。</p><p>并且，我还将介绍如何将这些工具全包含进 CI pipeline，从而实现自动化和高效。</p><h2>1.Pipenv</h2><p>它是为Python 设计的开发管理和依赖管理的工具，最早由&nbsp;Requests&nbsp;的作者&nbsp;Kenneth Reitz&nbsp;编写。</p><p>如果你用 python 做过一段时间的开发，那么管理环境，你可能用过&nbsp;virtualenv&nbsp;或&nbsp;venv&nbsp;；依赖管理可能用过较可靠的pip freeze &gt; requirements.txt。</p><p>大多数情况下，这完全没问题。但是，我发现 pipenv 更方便，且很强大，加上它通过Pipfile和Pipfile.lock近乎去掉固定依赖的做法，很大程度上替代了requirements.txt，从而带来更可靠的部署。</p><p>不过，我对 pipenv 的未来有点担忧，因为 Python 基金会已搁置对 pip 的改进。而且，pipenv 在 2019 年缺乏实质性进展。但是，我仍然认为，对大多数 python 用户来说，pipenv 是绝佳的工具。</p><p>官网下载地址</p><p>月下载量： 2111976</p><p>备选方案：&nbsp;poetry&nbsp;、&nbsp;virtualenv&nbsp;、&nbsp;venv</p><p><img src=\"https://static001.infoq.cn/resource/image/0a/cb/0ac04953d9c3d17bf5f6b6997a779ecb.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><h2>2.Ochrona</h2><p>这里，我有点私心，因为 Ochrona 是我积极开发并希望 2020 年发布的工具。不过，我还会介绍这个工具的替代方案。</p><p>Ochrona 是一款依赖分析和软件组成分析的工具，它可以用来检查你的开源依赖是否存在已知漏洞。这个领域，另一款很流行的开源工具是 pyup.io 的&nbsp;Safety&nbsp;。</p><p>我认为，Ochrona 比 Safety 更好的地方在于：</p><ol><li>无论是用于开源项目还是商业项目，它都提供免费方案，而且免费方案始终跟进最新的漏洞信息。</li><li>磁盘和 IO 使用非常少。不同于需要拉取整个漏洞数据库的本地工具，它是 SaaS 模式，只需调用一次公开的 API。</li><li>它提供优秀的漏洞数据并且每天更新，并比其他工具提供更多的漏洞详细信息，包括免费用户。</li></ol><p>官网下载地址</p><p>月下载量： 尚未发布</p><p>备选方案：&nbsp;safety&nbsp;、&nbsp;snyk&nbsp;(收费)</p><p><img src=\"https://static001.infoq.cn/resource/image/73/20/73bb1b1f17b2941c74f03f5b7baed220.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><h2>3.Bandit</h2><p>如果必须推荐一个可提高 python 项目安全性的工具，那我推荐&nbsp;Bandit&nbsp;。</p><p>据悉，Bandit 出自 OpenStack，但现在由&nbsp;PyCQA&nbsp;维护。它是一款开源的 SAST（静态应用安全测试）工具，免费、可配置且快速。从某些方面来讲，它就像是关注安全领域的 linter。</p><p>Bandit 很适合用来发现问题，比如不安全的配置、已知的不安全模块使用情况等。</p><p>官网下载地址</p><p>月下载量： 575101</p><p>备选方案：&nbsp;pyre&nbsp;、&nbsp;pyt&nbsp;、&nbsp;dodgy</p><p><img src=\"https://static001.infoq.cn/resource/image/81/5d/8186f94fe7feab5fb5a235799f7eec5d.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><h2>4.Black</h2><p>Black 是一款独特的代码格式化工具。它能自动将你的代码更正为 Black 样式（一个 Pep-8 的超集）。</p><p>传统的 linter 通常需要你把代码改为合规代码，而 Black 可以节省不少时间。并且，Black 只需有限的配置，这意味着你如果用过 Black，其他任何项目你都会觉得眼熟。</p><p>官网下载地址</p><p>月下载量： 1891711</p><p>备选方案：flake8、pylint</p><p><img src=\"https://static001.infoq.cn/resource/image/9b/35/9b8ef5b5261e1f3110fbe0dbb32d9c35.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><h2>5.Mypy</h2><p>它是python 一个可选的静态类型检查器。&nbsp;PEP 484&nbsp;引入 python 的类型提示，Mypy 则利用这些类型提示对项目进行静态类型检查。</p><p>Python 依然有动态的 duck 类型，不过，添加静态类型检查能帮你减少测试和调试时间，更早发现错误。</p><p>目前，大公司也在跟进 python 的静态类型检查。在 Guido van Rossum 任职期间，Dropbox 用 Mypy 检查了 400 多万行代码。其他的 python 用户，比如 Instagram 也开始做静态类型检查。</p><p>官网下载地址</p><p>月下载量： 2487228</p><p>备选方案：&nbsp;pyre</p><p><img src=\"https://static001.infoq.cn/resource/image/06/18/061ec86ab8565a673459ad83368fc418.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><h2>全部集成到一起</h2><p>这个例子种，我会用到&nbsp;Travis-CI&nbsp;，配置其他 CI 工具的过程与之类似相似，只是语法上会有差异。这里，我用一个简单、不安全且有问题的 flask 应用作为例子。</p><p>app.py文件如下：</p><p>复制代码</p><pre><table><tbody><tr><td><div data-line-number=\"1\">\n</div></td><td><p>from flask import Flask </p></td></tr><tr><td><div data-line-number=\"2\">\n</div></td><td><p>     </p></td></tr><tr><td><div data-line-number=\"3\">\n</div></td><td><p>app = Flask(__name__) </p></td></tr><tr><td><div data-line-number=\"4\">\n</div></td><td><p>    </p></td></tr><tr><td><div data-line-number=\"5\">\n</div></td><td><p>@app.route(&#39;/&lt;name&gt;&#39;) </p></td></tr><tr><td><div data-line-number=\"6\">\n</div></td><td><p>def hello_world(name: str) -&gt; str: </p></td></tr><tr><td><div data-line-number=\"7\">\n</div></td><td><p>    return hello_name(name)</p></td></tr><tr><td><div data-line-number=\"8\">\n</div></td><td><p>def hello_name(name: str) -&gt; int:</p></td></tr><tr><td><div data-line-number=\"9\">\n</div></td><td><p>    return f&quot;hello, {name}&quot;</p></td></tr><tr><td><div data-line-number=\"10\">\n</div></td><td><p>    </p></td></tr><tr><td><div data-line-number=\"11\">\n</div></td><td><p>if __name__ == &#39;__main__&#39;: </p></td></tr><tr><td><div data-line-number=\"12\">\n</div></td><td><p>    app.run(debug=True)</p></td></tr></tbody></table></pre><p>Pipfile如下：</p><p>复制代码</p><pre><table><tbody><tr><td><div data-line-number=\"1\">\n</div></td><td><p>[[source]]</p></td></tr><tr><td><div data-line-number=\"2\">\n</div></td><td><p>name = &quot;pypi&quot;</p></td></tr><tr><td><div data-line-number=\"3\">\n</div></td><td><p>url = &quot;https://pypi.org/simple&quot;</p></td></tr><tr><td><div data-line-number=\"4\">\n</div></td><td><p>verify_ssl = true</p></td></tr><tr><td><div data-line-number=\"5\">\n</div></td><td><p>\n</p></td></tr><tr><td><div data-line-number=\"6\">\n</div></td><td><p>[dev-packages]</p></td></tr><tr><td><div data-line-number=\"7\">\n</div></td><td><p>bandit = &quot;*&quot;</p></td></tr><tr><td><div data-line-number=\"8\">\n</div></td><td><p>v = {editable = true,version = &quot;*&quot;}</p></td></tr><tr><td><div data-line-number=\"9\">\n</div></td><td><p>black = &quot;*&quot;</p></td></tr><tr><td><div data-line-number=\"10\">\n</div></td><td><p>mypy = &quot;*&quot;</p></td></tr><tr><td><div data-line-number=\"11\">\n</div></td><td><p>ochrona = &quot;*&quot;</p></td></tr><tr><td><div data-line-number=\"12\">\n</div></td><td><p>\n</p></td></tr><tr><td><div data-line-number=\"13\">\n</div></td><td><p>[packages]</p></td></tr><tr><td><div data-line-number=\"14\">\n</div></td><td><p>flask = &quot;==0.12.2&quot;</p></td></tr><tr><td><div data-line-number=\"15\">\n</div></td><td><p>\n</p></td></tr><tr><td><div data-line-number=\"16\">\n</div></td><td><p>[requires]</p></td></tr><tr><td><div data-line-number=\"17\">\n</div></td><td><p>python_version = &quot;3.7&quot;</p></td></tr></tbody></table></pre><p>最后在根目录下创建一个.travis.yml文件，内容如下：</p><p>复制代码</p><pre><table><tbody><tr><td><div data-line-number=\"1\">\n</div></td><td><p>language: python</p></td></tr><tr><td><div data-line-number=\"2\">\n</div></td><td><p>python:</p></td></tr><tr><td><div data-line-number=\"3\">\n</div></td><td><p>  - 3.7</p></td></tr><tr><td><div data-line-number=\"4\">\n</div></td><td><p>install:</p></td></tr><tr><td><div data-line-number=\"5\">\n</div></td><td><p>  - pip install -U pip</p></td></tr><tr><td><div data-line-number=\"6\">\n</div></td><td><p>  - pip install pipenv</p></td></tr><tr><td><div data-line-number=\"7\">\n</div></td><td><p>  - pipenv install --dev</p></td></tr><tr><td><div data-line-number=\"8\">\n</div></td><td><p>script:</p></td></tr><tr><td><div data-line-number=\"9\">\n</div></td><td><p>  - bandit ./*</p></td></tr><tr><td><div data-line-number=\"10\">\n</div></td><td><p>  - black --check .</p></td></tr><tr><td><div data-line-number=\"11\">\n</div></td><td><p>  - ochrona</p></td></tr><tr><td><div data-line-number=\"12\">\n</div></td><td><p>  - mypy .</p></td></tr></tbody></table></pre><p>如果查看这里的构建，你会发现每个工具都标出错误或指出需修改的地方。那么，我们来做一些修正，如这个 PR&nbsp;所示，构建就可以通过。</p><p><img src=\"https://static001.infoq.cn/resource/image/fa/50/faa90fd7b730dbf5adf6f205bfe88c50.png\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><p>将 Flask 升级到一个没有已知漏洞的版本</p><p><img src=\"https://static001.infoq.cn/resource/image/51/20/51dee83cd02c91d5fc6f123bad1e7f20.png\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><p>修复类型注释，禁用调试模式，规范格式</p><p>虽然这个例子只涉及一个 CI 平台，但其实和集成到其他大多数平台的方法都很相似。</p><p>下面是一个总的评分表：</p><p><img src=\"https://static001.infoq.cn/resource/image/28/f6/28d907b9bd2ba1998c1d83bd541c63f6.jpg\" alt=\"开发安全、高质量代码的5款顶级Python工具\" class=\"fr-fic fr-dii\"></p><p>英文原文：</p><p>Top Python Tools for Developing Secure, Quality Code</p><p><br></p>', 0, '为提高代码的质量、安全性和可维护性，软件工程师每天会用到无数工具。我会列出一些自己最喜欢的 python 工具，并从易用性（是否易于安装、运行和自动化）、质量影响（能否阻止可预见的 bug）、可维护性', NULL, '开发安全、高质量代码的 5 款顶级 Python 工具', 4, 3, 2, '2020-03-12 18:17:24', 0, '2020-03-13 00:00:31', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (19, NULL, '<p>一、大麦抢票背景</p><p>大麦网主要的业务范围为演唱会、音乐会、体育赛事、话剧、展销会、亲子活动等现场类的票务业务，其业务链条涵盖从 B 端生产、C 端销售、现场换验的全套流程。大麦网一类典型项目是稀缺的火爆 IP 项目，如演唱会、游戏体育赛事，这类票务隐含了时间、空间的特殊限制属性，是需要抢的。大麦抢票是演出行业的双 11，涉及场景复杂、系统较多、链路较长，抢票保障尤为重要。</p><p>大麦抢票保障大致经历了几个阶段：</p><p>第一阶段：&ldquo;原始&rdquo;阶段，保障不健全，设施不完善；</p><p>第二阶段：&ldquo;弹内化&rdquo;阶段，部分大型抢票顺利完成；</p><p>第三阶段：&ldquo;体系化&rdquo;阶段，能够承接所有大型抢票；</p><p>第四阶段：&ldquo;常态化&rdquo;阶段，大抢体验优化升级。</p><p><img src=\"https://static001.infoq.cn/resource/image/5a/68/5ae9eba4cf6f0686e4b43df3dc0c1f68.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>二、&ldquo;原始&rdquo;阶段</p><p>大抢容易出现限流，体验不顺畅等现象，囧！</p><ol><li>为何会这样</li></ol><p>此阶段的大麦还处在原技术团队和阿里系业务、产品、技术各方都在讨论及对焦阶段，大部分大型抢票核心系统还在大麦的 IDC 机房，技术体系走.net 和 java 的混合体系，大麦原体系主要承载此阶段的大型抢票任务。</p><p>为什么此体系下，每逢大型抢票就会面临如此大的压力和风险呢？分析主要有以下几点原因：</p><p>1）保障设施不健全：大麦 IDC 机房硬件设备、带宽等均有限制；DB 采用 SQL SERVER 企业版，很多数据库都是单库。在应对大型抢票时（特别是选座购买，耗带宽、耗资源），会面临严峻挑战；</p><p>2）预案 / 限流待建设：系统在高流量、高压力下的保护措施待建设，比如：限流和降级，造成系统一旦超过压力，就直接报警；</p><p>3）监控运维零散：定位问题、解决问题耗时较长。</p><ol start=\"2\"><li>方案和结果</li></ol><p>这阶段也采取了一些临时方案，比如：极简限流方案、一些点的性能优化、修改应用配置参数、整理抢票预案等等，也缓解了一些问题，但整体上仍未解决大型抢票问题。</p><p>三、&ldquo;弹内化&rdquo;阶段</p><p>基于第一阶段的诸多问题，产品、技术已经启动大面积系统改造，直接重构新系统到阿里域内，用新系统建设逐步替代老系统，即空中换引擎方案。</p><ol><li>空中换引擎</li></ol><p>要快速将关键系统重构到阿里域内，确立了直接迁移、临时方案或长期重构等步骤，具体方案是：</p><p>1）APP 链路部分弹内化：技术改造重点放在无线端，所有 APP 用户调用接口的入口先走到阿里域，再路由到大麦 IDC，让阿里机房来抵档大量流量；</p><p>2）借助阿里基础运维能力：由于入口接口入到阿里域，一些限流及降级的事情利用平台就可以做，运维监控也完全可以利用 eagleeye、maieye 等排查工具来做了；此过程中</p><p>用户中心、消息中心等服务开始向阿里域内重构并上线；</p><p>3）抢票预案初步建立：在主站的预案平台建立了大麦的大型抢票预案，比如：商品详情页增加了 tair 缓存，靠 tair 在阿里域内扛住流量，减少打到大麦 IDC 的请求调用；</p><ol start=\"2\"><li>坚定路线不动摇</li></ol><p>优化后的热量抢票项目系统正常，但限流会影响用户体验。分析抢票过程中出现的问题，集中在了弹内化范围、机房的瓶颈、运维经验不足等。所以，催生了第三阶段，大型抢票全链路收口进阿里域内。</p><p>四、&ldquo;体系化&rdquo;阶段</p><p>针对上阶段遗留的问题，业务和技术针对抢票流程和系统做了全体系化升级，确立了完善的抢票流程和抢票保障机制。升级后的大麦能承接住所有大型抢票，且用户体验有所提升。</p><ol><li>弹内化全面开花</li></ol><p>1）新搜索在阿里域内上线，搜索 response 过大的问题也都解决，不存在对带宽的影响了；</p><p>2）新选座在阿里域内上线，大型抢票的选座流量直接打到阿里域内，利用异步化和类似 ConcurrentHashMap 的机制平衡了对大麦 IDC 选座的调用量及缓存的一致性；</p><p>3）新交易在阿里域内上线，将核心的创建事务号接口、下单接口全部都放在了阿里域内，单下单后订单要同步到大麦机房的进行后续履约服务；</p><ol start=\"2\"><li>保障流程建起来</li></ol><p>基于抢票活动业务方和技术方信息不一致、或临时抢票活动准备不充分等情况，由测试方牵头和各业务方、各技术方针对抢票流程、参与人员、抢票设置各项达成一致，并沉淀出抢票保障流程和方案，相关人为操作建立 SOP 保障，优化后的流程为：</p><p><img src=\"https://static001.infoq.cn/resource/image/cd/a6/cdd41d03d81dd0adb4b7d5811a7c5ca6.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>1）【流程建设】抢票阶段分为抢票前、抢票中、抢票后：</p><p>抢票前重点是由业务方抢票申报，再由技术方确认是否安排预演或压测，根据业务方和历史抢票信息判断抢票级别来决定抢票预案执行范围和风控级别；</p><p>抢票中重点是过程监控和应急处理；</p><p>抢票后重点是预案恢复、抢票报表输出，以及抢票过程中问题复盘；</p><p>2）【流程建设】抢票参与角色中：产品、开发、测试、公关</p><p>抢票涉及多个业务方，主要业务保障人是 BD、编辑、客服，主要支持角色有开发、测试、客服、公关等相关人员；</p><p>抢票过程中相关角色各司其职，共同保障抢票。印象比较深的就是这个阶段每逢大抢，一屋子相关抢票人员聚集，在抢票顺利完成后，会议室传出的阵阵欢呼声！</p><ol start=\"3\"><li>预案 / 预演 / 容量 /Action 专项</li></ol><p>抢票保障的每一项操作都需要在业务方明确项目信息后，由测试牵头拉各方参与人员整体评估和协调执行，不管是抢票前的准备还是抢票后的复盘，每个小的专项都执行到位。</p><p>1）【质量保障】项目预演：一般已有成熟流程的项目不会再安排预演，对大型抢票或未抢过的新玩法，测试方会安排模拟抢票。主要由各业务方、技术方和各端测试同学共同参与，提前暴露业务、设置问题或体验问题；</p><p>2）【质量保障】性能容量：技术拉取全链路最近类似项目的最新压测数据，和线上实际容量做评估，分析抢票预估量是否能顺利支撑，是否有性能瓶颈或限流情况，提前通知业务方；如项目玩法没有最近的压测数据支撑，由测试方安排大抢项目压测；</p><p>3）【技术优化】预案执行：全链路涉及的首页搜索、商品详情、票务云选座、交易下单、票务云库存、订单服务、无线端等梳理大抢模式的前置预案和紧急预案。如商品详情前置预案：对艺人、关注、营销等降级处理，调整用户、场馆、详情缓存时长，抢票前 30 分钟预热限购服务的 BD 和 tair 等；</p><p>4）【质量保障】问题复盘：每次抢票完成后对抢票过程中各方暴露的问题、客服反馈的高咨询、线上收集的 bug、内网帖子吐糟、外部用户微博或微信转载等问题，测试方统一收集，组织各方复盘后优化方案落实到 action，并跟进 action 执行进度；</p><ol start=\"4\"><li>抢票监控大盘</li></ol><p>除各业务定制的抢票监控项外，抢票期大盘的汇总数据监控，可以为每次抢票更好地提供监控数据支持，方便业务方一目了然 get 到抢票数据，具体信息如下：</p><p><img src=\"https://static001.infoq.cn/resource/image/c7/20/c7cfc4aa371ca9148043211943072e20.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>五、&ldquo;常态化&rdquo;阶段</p><p>上阶段系统化升级完善后，大家肯定会问，大麦抢票不会出问题了吧？</p><p>我们可以很负责地说肯定再不会出现宕机情况，但是在近期超稀缺 IP 抢票因为项目太火爆，抢到票的毕竟是少数人，大抢之后出现的二手高价售卖现象、抢票不流畅导致抢不到票等问题，被抢不到票的内网小二、外网用户疯狂吐槽。</p><p>总结槽点主要集中在抢不到票、商品详情被限流、下单交互耗时导致抢不到票、抢票异常情况对用户提示不友好等等，针对这些问题除了产品方案的优化，技术同学也成立了很多抢票专项解决用户痛点，这些小而美的体验极致优化，你注意到了吗？</p><ol><li>为真实用户护航</li></ol><p>此阶段磨砺了近一年的大麦新交易系统上线了，新交易和共享星环平台全面融合，核心的渲染接口、下单接口基于星环能力实现了大麦扩展特性；新交易架构图如下：</p><p><img src=\"https://static001.infoq.cn/resource/image/56/6f/56416dc51c396de3bd698b5f1234086f.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>融入共享对大麦来说好处很多，针对抢票流程的贡献主要有 3 点：</p><p>1）【技术优化】依托共享基础能力</p><p>除了可以复用共享能力，还可以参考主站交易的大抢方案，比如限流、系统日志监控、问题排查平台等等。技术方实现基于星环体系的未支付关单定制，由业务方指定关单时长，有效降低了恶意占用库存现象发生。</p><p>2）【体验升级】接入集团风控体系：</p><p>服务于线上火爆抢票的三层防控技术体系，实际由大麦用户风险评分、集团安全 MTEE 人机识别、定制化的策略包组成。在交易流程的渲染、下单以不同维度对非法用户进行二次拦截，让真实用户的抢票体验更顺滑，大大提升了真实用户的购买率。</p><p><img src=\"https://static001.infoq.cn/resource/image/12/21/1202b6f59fd7639c776daaaabd3b7221.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><ol start=\"2\"><li>核心链路模型优化</li></ol><p>1）【体验升级】商品详情无限流：从以往抢票看，商品详情页每逢大抢不仅要借机器还要限流，成为了用户槽点聚集地。商品详情主要实现了流量分散策略：</p><p>a）策略上减少开抢前并发请求，由于散列控制在较短时间，能够快速上线快速验证，但效果不明显；</p><p>b）交互上倒计时结束后用户点击替代自动刷新来分散流量，效果明显</p><p>c）流程上减少物理调用，倒计时用户点击购买时只调用二级页，倒计时校验交给二级页，效果非常明显</p><p><img src=\"https://static001.infoq.cn/resource/image/cd/ee/cd35afc4d6e0b014f388fe4fe60421ee.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>改造后详情页与弹层页流量从巨大差距到基本持平再到流量较大反转，商品详情抢票再也不用借机器，且足以支撑目标最高热门项目抢票，再不会触发限流！</p><p>2）【体验升级】交易下单扩入口：从以往抢票看，交易下单大流量时容易触发限流，用户反馈抢不到票。针对交易下单的主要策略是：</p><p>a）放大入口 + 风控拦截 + 兜底限流，首先让真实用户能够进到交易，再通过风控过滤掉异常用户，最后用兜底限流保护下游系统，从而最大限度的保障用户抢票顺滑；</p><p>b）对渲染阶段的支付方式和支付特权做优化，实现大抢时渲染对支付方式调用降级，降低用户渲染或异步渲染被流控的风险；</p><p><img src=\"https://static001.infoq.cn/resource/image/d5/38/d5f7a66cebc76395b7126e1e9a95e238.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><ol start=\"3\"><li>性能常态化</li></ol><p>为摸清交易链路最新性能水位，测试团队启动了性能常态化项目。每月定时执行压测，并结合当月各系统功能发布、性能优化或上下游支持，评估具体的压测场景和压测目标，压测完毕后更新链路依赖现状，为抢票提供最有效数据。</p><p>1）【质量保障】常态化执行：如近期的稀缺 IP 项目抢票再次刷新各榜单列表，开发和测试一起根据现有流量重新对系统进行压测摸高评估。</p><p>2）【质量保障】压测自动化：测试梳理了压测流程，沉淀了各业务线压测白皮书。提炼出过程中耗时较多的步骤，实现自动化执行，如原临时项目生成压测数据和压测前设置需要 11-12 步耗时半天，现在常态化固定项目生成压测数据和仅需要 3-4 步 3 分钟即可。</p><p><img src=\"https://static001.infoq.cn/resource/image/8c/cf/8c79cde33025ce28e38d6038f82a81cf.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><ol start=\"4\"><li>预案自动化</li></ol><p>之前抢票存在抢票活动频繁，抢票无统一规范流程（监控、预案、入口），支持人数多，组织抢票会议，耗费人力等问题。</p><p>1）【技术优化】抢票控制台：</p><p>使 BD 或者运营在【抢票开始前】可以设置一些预案，【抢票过程中】提供统一视图对抢票进行【实时监控】，并且有能力进行【人为的干预和控制】，在【抢票结束后】能够提供历次抢票数据以供分析，从而帮助 BD 自助完成抢票，实现&ldquo;无人值守&rdquo;具体实现流程如下：</p><p><img src=\"https://static001.infoq.cn/resource/image/60/27/6045f583c0555f5b34933179d168b027.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>2）【技术优化】商品详情预案：</p><p>详情页每次大抢都需要人工降级 6~10 项预案，各项预案设置的值、执行时间等各有差异，在预案设置时还需根据个人经验做评估调整，人工操作太繁琐。详情预案基础策略是对原降级项实现本地缓存或 tair 缓存，第三方依赖接口限流异常降级补充，优化后仅保留 1 项需手动配置的预案，其他皆已实现自动化。</p><ol start=\"5\"><li>常态化成果</li></ol><p>通过常态化各专项保障取得明显成效，近一年支持的抢票项目近千场，其中大型抢票近百场。其中近期热门「超大抢」项目，商详 / 下单渲染 / 下单均创历史峰值，系统顺利承接；热门「大抢」项目，特权码选座和普通选座，特权及选座峰值均创新高，系统顺利承接；</p><p><img src=\"https://static001.infoq.cn/resource/image/81/50/81831786bf3682e9804abfbb20bfa650.png\" alt=\"高并发大流量，大麦抢票的技术涅槃之路\" class=\"fr-fic fr-dii\"></p><p>六、结语</p><p>大麦抢票经历了&ldquo;原始&rdquo;阶段、流程建设、技术优化、专项保障等四个阶段建设后系统已稳如磐石，对提升用户体验上也在不断努力。当然可能还是存在或多或少的问题，目前的技术方案也可能不是最优的，比如项目热度智能分析、风控自动调节等等，也已经在技术优化计划中，在抢票的路上大麦会秉持初心一直奔跑！</p><p><br></p>', 0, '一、大麦抢票背景大麦网主要的业务范围为演唱会、音乐会、体育赛事、话剧、展销会、亲子活动等现场类的票务业务，其业务链条涵盖从 B 端生产、C 端销售、现场换验的全套流程。大麦网一类典型项目是稀缺的火爆 ', NULL, '高并发大流量，大麦抢票的技术涅槃之路', 0, 16, 0, '2020-03-12 18:20:02', 0, '2020-03-12 18:20:02', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (20, NULL, '<p>在持久化数据对象的时候我们很少使用 Java 序列化，而是使用数据库等方式来实现。但是在我看来，Java 序列化是一个很重要的内容，序列化不仅可以保存对象到磁盘进行持久化，还可以通过网络传输。在平时的面试当中，序列化也是经常被谈及的一块内容。</p><p>谈到序列化时，大家可能知道将类实现 Serializable 接口就可以达到序列化的目的，但当看到关于序列化的面试题时我们却常常一脸懵逼。</p><p>1）可序列化接口和可外部接口的区别是什么？</p><p>2）序列化时，你希望某些成员不要序列化？该如何实现？</p><p>3）什么是 serialVersionUID ？如果不定义 serialVersionUID，会发生什么？</p><p>是不是突然发现我们对这些问题其实都还存在很多疑惑？本文将总结一些 Java 序列化的常见问题，并且通过 demo 来进行测试和解答。</p><p>一、什么是 Java 序列化？</p><p>序列化是把对象改成可以存到磁盘或通过网络发送到其它运行中的 Java 虚拟机的二进制格式的过程，并可以通过反序列化恢复对象状态。Java 序列化 API 给开发人员提供了一个标准机制：通过实现 java.io.Serializable 或者 java.io.Externalizable 接口，ObjectInputStream 及 ObjectOutputStream 处理对象序列化。实现 java.io.Externalizable 接口的话，Java 程序员可自由选择基于类结构的标准序列化或是它们自定义的二进制格式，通常认为后者才是最佳实践，因为序列化的二进制文件格式成为类输出 API 的一部分，可能破坏 Java 中私有和包可见的属性的封装。</p><p>序列化到底有什么用？</p><p>实现 java.io.Serializable。</p><p>定义用户类：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>class User implements Serializable {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; private String username;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; private String passwd;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; public String getUsername() {</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return username;</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; public void setUsername(String username) {</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.username = username;</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; public String getPasswd() {</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return passwd;</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; public void setPasswd(String passwd) {</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.passwd = passwd;</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>}</p></td></tr></tbody></table><p><br></p><p>我们把对象序列化，通过 ObjectOutputStream 存储到 txt 文件中，再通过 ObjectInputStream 读取 txt 文件，反序列化成 User 对象。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>public class TestSerialize {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; public static void main(String[] args) {</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; User user = new User();</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; user.setUsername(&quot;hengheng&quot;);</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; user.setPasswd(&quot;123456&quot;);</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;read before Serializable: &quot;);</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;username: &quot; + user.getUsername());</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.err.println(&quot;password: &quot; + user.getPasswd());</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; try {</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ObjectOutputStream os = new ObjectOutputStream(</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new FileOutputStream(&quot;/Users/admin/Desktop/test/user.txt&quot;));</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.writeObject(user); // 将 User 对象写进文件</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.flush();</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.close();</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (FileNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"21\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {</p></td></tr><tr><td><div data-line-number=\"22\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"23\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"24\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; try {</p></td></tr><tr><td><div data-line-number=\"25\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ObjectInputStream is = new ObjectInputStream(new FileInputStream(</p></td></tr><tr><td><div data-line-number=\"26\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;/Users/admin/Desktop/test/user.txt&quot;));</p></td></tr><tr><td><div data-line-number=\"27\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; user = (User) is.readObject(); // 从流中读取 User 的数据</p></td></tr><tr><td><div data-line-number=\"28\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is.close();</p></td></tr><tr><td><div data-line-number=\"29\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"30\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;\\nread after Serializable: &quot;);</p></td></tr><tr><td><div data-line-number=\"31\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;username: &quot; + user.getUsername());</p></td></tr><tr><td><div data-line-number=\"32\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.err.println(&quot;password: &quot; + user.getPasswd());</p></td></tr><tr><td><div data-line-number=\"33\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"34\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (FileNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"35\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"36\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {</p></td></tr><tr><td><div data-line-number=\"37\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"38\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (ClassNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"39\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"40\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"41\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"42\"><br></div></td><td><p>}</p></td></tr></tbody></table><p><br></p><p>运行结果如下：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>序列化前数据:</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>password: 123456</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>序列化后数据:</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>password: 123456</p></td></tr></tbody></table><p><br></p><p>到这里，我们大概知道了什么是序列化。</p><p>二、序列化时如何保证某些成员不被序列化？</p><p>答案：声明该成员为静态或瞬态，在 Java 序列化过程中则不会被序列化。</p><ul><li>静态变量：加 static 关键字。</li><li>瞬态变量：加 transient 关键字。</li></ul><p>我们先尝试把变量声明为瞬态。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>class User implements Serializable {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; private String username;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; private transient String passwd;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; public String getUsername() {</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return username;</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; public void setUsername(String username) {</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.username = username;</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; public String getPasswd() {</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return passwd;</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; public void setPasswd(String passwd) {</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.passwd = passwd;</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr></tbody></table><p><br></p><p>在密码字段前加上了 transient 关键字再运行。运行结果：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>序列化前数据:</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>password: 123456</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>序列化后数据:</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>password: null</p></td></tr></tbody></table><p><br></p><p>通过运行结果发现密码没有被序列化，达到了我们的目的。</p><p>再尝试在用户名前加static关键字。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>class User implements Serializable {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; private static String username;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; private transient String passwd;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; public String getUsername() {</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return username;</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; public void setUsername(String username) {</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.username = username;</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; public String getPasswd() {</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return passwd;</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; public void setPasswd(String passwd) {</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.passwd = passwd;</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr></tbody></table><p><br></p><p>运行结果：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>序列化前数据:</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>password: 123456</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>序列化后数据:</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>password: null</p></td></tr></tbody></table><p><br></p><p>我们发现运行后的结果和预期的不一样，按理说 username 也应该变为 null 才对。是什么原因呢？</p><p>原因是：反序列化后类中 static 型变量 username 的值为当前 JVM 中对应的静态变量的值，而不是反序列化得出的。</p><p>我们来证明一下：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>public class TestSerialize {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; public static void main(String[] args) {</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; User user = new User();</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; user.setUsername(&quot;hengheng&quot;);</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; user.setPasswd(&quot;123456&quot;);</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot; 序列化前数据: &quot;);</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;username: &quot; + user.getUsername());</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.err.println(&quot;password: &quot; + user.getPasswd());</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; try {</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ObjectOutputStream os = new ObjectOutputStream(</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new FileOutputStream(&quot;/Users/admin/Desktop/test/user.txt&quot;));</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.writeObject(user); // 将 User 对象写进文件</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.flush();</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.close();</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (FileNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"21\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {</p></td></tr><tr><td><div data-line-number=\"22\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"23\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"24\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; User.username = &quot; 小明 &quot;;</p></td></tr><tr><td><div data-line-number=\"25\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; try {</p></td></tr><tr><td><div data-line-number=\"26\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ObjectInputStream is = new ObjectInputStream(new FileInputStream(</p></td></tr><tr><td><div data-line-number=\"27\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;/Users/admin/Desktop/test/user.txt&quot;));</p></td></tr><tr><td><div data-line-number=\"28\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; user = (User) is.readObject(); // 从流中读取 User 的数据</p></td></tr><tr><td><div data-line-number=\"29\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is.close();</p></td></tr><tr><td><div data-line-number=\"30\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"31\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;\\n 序列化后数据: &quot;);</p></td></tr><tr><td><div data-line-number=\"32\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;username: &quot; + user.getUsername());</p></td></tr><tr><td><div data-line-number=\"33\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.err.println(&quot;password: &quot; + user.getPasswd());</p></td></tr><tr><td><div data-line-number=\"34\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"35\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (FileNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"36\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"37\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {</p></td></tr><tr><td><div data-line-number=\"38\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"39\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (ClassNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"40\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"41\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"42\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"43\"><br></div></td><td><p>}</p></td></tr><tr><td><div data-line-number=\"44\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"45\"><br></div></td><td><p>class User implements Serializable {</p></td></tr><tr><td><div data-line-number=\"46\"><br></div></td><td><p>&nbsp; &nbsp; public static String username;</p></td></tr><tr><td><div data-line-number=\"47\"><br></div></td><td><p>&nbsp; &nbsp; private transient String passwd;</p></td></tr><tr><td><div data-line-number=\"48\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"49\"><br></div></td><td><p>&nbsp; &nbsp; public String getUsername() {</p></td></tr><tr><td><div data-line-number=\"50\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return username;</p></td></tr><tr><td><div data-line-number=\"51\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"52\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"53\"><br></div></td><td><p>&nbsp; &nbsp; public void setUsername(String username) {</p></td></tr><tr><td><div data-line-number=\"54\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.username = username;</p></td></tr><tr><td><div data-line-number=\"55\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"56\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"57\"><br></div></td><td><p>&nbsp; &nbsp; public String getPasswd() {</p></td></tr><tr><td><div data-line-number=\"58\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return passwd;</p></td></tr><tr><td><div data-line-number=\"59\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"60\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"61\"><br></div></td><td><p>&nbsp; &nbsp; public void setPasswd(String passwd) {</p></td></tr><tr><td><div data-line-number=\"62\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.passwd = passwd;</p></td></tr><tr><td><div data-line-number=\"63\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"64\"><br></div></td><td><p>}</p></td></tr></tbody></table><p><br></p><p>在反序列化前把静态变量 username 的值改为『小明』。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>User.username = &quot; 小明 &quot;;</p></td></tr></tbody></table><p><br></p><p>再运行一次：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>序列化前数据:</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>password: 123456</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>序列化后数据:</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>username: 小明</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>password: null</p></td></tr></tbody></table><p><br></p><p>果然，这里的 username 是 JVM 中静态变量的值，并不是反序列化得到的值。</p><p>三、serialVersionUID 有什么用？</p><p>我们经常会在类中自定义一个 serialVersionUID：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>private static final long serialVersionUID = 8294180014912103005L</p></td></tr></tbody></table><p><br></p><p>这个 serialVersionUID 有什么用呢？如果不设置的话会有什么后果？</p><p>serialVersionUID 是一个 private static final long 型 ID，当它被印在对象上时，它通常是对象的哈希码。serialVersionUID 可以自己定义，也可以自己去生成。</p><p>不指定 serialVersionUID 的后果是：当你添加或修改类中的任何字段时，已序列化类将无法恢复，因为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化的过程是依赖于正确的序列化对象恢复状态的，并在序列化对象序列版本不匹配的情况下引发 java.io.InvalidClassException 无效类异常。</p><p>举个例子大家就明白了：</p><p>我们保持之前保存的序列化文件不变，然后修改 User 类。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>class User implements Serializable {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; public static String username;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; private transient String passwd;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; &nbsp; private String age;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; public String getUsername() {</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return username;</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; public void setUsername(String username) {</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.username = username;</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; public String getPasswd() {</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return passwd;</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; public void setPasswd(String passwd) {</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.passwd = passwd;</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"21\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"22\"><br></div></td><td><p>&nbsp; &nbsp; public String getAge() {</p></td></tr><tr><td><div data-line-number=\"23\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return age;</p></td></tr><tr><td><div data-line-number=\"24\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"25\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"26\"><br></div></td><td><p>&nbsp; &nbsp; public void setAge(String age) {</p></td></tr><tr><td><div data-line-number=\"27\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.age = age;</p></td></tr><tr><td><div data-line-number=\"28\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"29\"><br></div></td><td><p>}</p></td></tr></tbody></table><p><br></p><p>加了一个属性 age，然后单另写一个反序列化的方法：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>public static void main(String[] args) {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; try {</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ObjectInputStream is = new ObjectInputStream(new FileInputStream(</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;/Users/admin/Desktop/test/user.txt&quot;));</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; User user = (User) is.readObject(); // 从流中读取 User 的数据</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is.close();</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;\\n 修改 User 类之后的数据: &quot;);</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;username: &quot; + user.getUsername());</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.err.println(&quot;password: &quot; + user.getPasswd());</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (FileNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; } catch (ClassNotFoundException e) {</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr></tbody></table><p><br></p><p><img src=\"https://static001.infoq.cn/resource/image/1d/dc/1d6484aa0a3a9ae2c8088f8ced7619dc.png\" alt=\"关于Java序列化的问题你真的会吗？\" class=\"fr-fic fr-dii\"></p><p>报错了，我们发现之前的 User 类生成的 serialVersionUID 和修改后的 serialVersionUID 不一样（因为是通过对象的哈希码生成的），导致了 InvalidClassException 异常。</p><p>自定义 serialVersionUID：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>class User implements Serializable {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; private static final long serialVersionUID = 4348344328769804325L;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; &nbsp; public static String username;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; private transient String passwd;</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; private String age;</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; &nbsp; public String getUsername() {</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return username;</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp; &nbsp; public void setUsername(String username) {</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.username = username;</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; public String getPasswd() {</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return passwd;</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>&nbsp; &nbsp; public void setPasswd(String passwd) {</p></td></tr><tr><td><div data-line-number=\"21\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.passwd = passwd;</p></td></tr><tr><td><div data-line-number=\"22\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"23\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"24\"><br></div></td><td><p>&nbsp; &nbsp; public String getAge() {</p></td></tr><tr><td><div data-line-number=\"25\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; return age;</p></td></tr><tr><td><div data-line-number=\"26\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"27\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"28\"><br></div></td><td><p>&nbsp; &nbsp; public void setAge(String age) {</p></td></tr><tr><td><div data-line-number=\"29\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; this.age = age;</p></td></tr><tr><td><div data-line-number=\"30\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"31\"><br></div></td><td><p>}</p></td></tr></tbody></table><p><br></p><p>再试一下：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>序列化前数据:</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>username: hengheng</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>password: 123456</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>序列化后数据:</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>username: 小明</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>password: null</p></td></tr></tbody></table><p><br></p><p>运行结果无报错，所以一般都要自定义 serialVersionUID。</p><p>四、是否可以自定义序列化过程？</p><p>答案当然是可以的。</p><p>之前我们介绍了序列化的第二种方式：</p><p>实现 Externalizable 接口，然后重写 writeExternal() 和 readExternal() 方法，这样就可以自定义序列化。</p><p>比如我们尝试把变量设为瞬态。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>public class ExternalizableTest implements Externalizable {</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; private transient String content = &quot; 我是被 transient 修饰的变量哦 &quot;;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; @Override</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; public void writeExternal(ObjectOutput out) throws IOException {</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; out.writeObject(content);</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; @Override</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; public void readExternal(ObjectInput in) throws IOException,</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ClassNotFoundException {</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; content = (String) in.readObject();</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; public static void main(String[] args) throws Exception {</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; ExternalizableTest et = new ExternalizableTest();</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; ObjectOutput out = new ObjectOutputStream(new FileOutputStream(</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new File(&quot;test&quot;)));</p></td></tr><tr><td><div data-line-number=\"21\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; out.writeObject(et);</p></td></tr><tr><td><div data-line-number=\"22\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"23\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; ObjectInput in = new ObjectInputStream(new FileInputStream(new File(</p></td></tr><tr><td><div data-line-number=\"24\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;test&quot;)));</p></td></tr><tr><td><div data-line-number=\"25\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; et = (ExternalizableTest) in.readObject();</p></td></tr><tr><td><div data-line-number=\"26\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(et.content);</p></td></tr><tr><td><div data-line-number=\"27\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"28\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; out.close();</p></td></tr><tr><td><div data-line-number=\"29\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; in.close();</p></td></tr><tr><td><div data-line-number=\"30\"><br></div></td><td><p>&nbsp; &nbsp; }</p></td></tr><tr><td><div data-line-number=\"31\"><br></div></td><td><p>}</p></td></tr></tbody></table><p><br></p><p>运行结果：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>我是被 transient 修饰的变量哦</p></td></tr></tbody></table><p><br></p><p>这里实现的是 Externalizable 接口，则没有任何东西可以自动序列化，需要在 writeExternal 方法中进行手工指定所要序列化的变量，这与是否被 transient 修饰无关。</p><p>通过上述介绍，是不是对 Java 序列化有了更多的了解？</p>', 0, '在持久化数据对象的时候我们很少使用 Java 序列化，而是使用数据库等方式来实现。但是在我看来，Java 序列化是一个很重要的内容，序列化不仅可以保存对象到磁盘进行持久化，还可以通过网络传输。在平时的', NULL, '关于 Java 序列化的问题你真的会吗？', 5, 16, 1, '2020-03-12 18:20:51', 0, '2020-04-04 11:13:51', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (21, NULL, '<p>物理学家理查德&middot; 费曼曾说过：</p><p><br></p><p>自然不是古板的，如果想要要模拟自然，最好使用量子力学定律。</p><p><br></p><p>2020 年 3 月 11 日，TensorFlow Dev Summit 峰会通过线上直播的方式与各位开发者见面。会上，谷歌介绍了近期宣布开源的一款用于训练量子模型的机器学习库 TensorFlow Quantum（简称 TFQ）。谷歌表示，该量子机器学习模型能够处理量子数据，并能够在量子计算机上执行。</p><p><img src=\"https://static001.infoq.cn/resource/image/6b/a1/6b9a2c87a5556cab5b5958a208c641a1.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>量子机器学习库 TensorFlow Quantum</p><p>机器学习（ML）虽然不能精确模拟自然界中的系统，但却能够学习系统模型并预测系统行为。在过去几年，经典的 ML 模型解决了科学领域诸多难题，在癌症检测、地震余震预测、极端天气预测以及系外行星探测等方面发挥了巨大作用。</p><p>近年来，随着量子计算技术的发展，在量子机器学习模型上的新发现将对世界级重大问题产生深远影响，从而带来医学、材料、传感和通信领域的突破。但是，迄今为止我们遇到的瓶颈是，缺乏研究工具来发现有用的、可以处理量子数据并能在计算机上使用的量子机器学习模型。</p><p>基于此，谷歌最神秘的部门 Google X 与滑铁卢大学和大众汽车公司等合作，联合发布 TensorFlow Quantum（TFQ），这是一个用于快速建立量子 ML 模型原型的开源库。TFQ 为量子计算和机器学习研究社区的结合提供了必要工具，从而控制 / 建模自然或人工量子系统，比如内含大约 50~100 量子比特的噪声中级量子处理器（NISQ）。</p><p>在底层，TFQ 集成了 NISQ 算法的开源框架 Cirq（Cirq 是 Google 专为 NISQ 算法打造的框架, 允许开发者为特定的量子处理器编写量子算法）和 TensorFlow，通过提供与现有 TensorFlow API 兼容的量子计算原语和高性能量子电路模拟器，为鉴别、生成量子经典模型的设计实现，提供高层次的抽象。</p><p>量子机器学习模型是什么？</p><p>量子模型能用量子力学原点表示和概括数据。但是，要了解量子模型，必须引入两个概念：量子数据模型和混合量子经典模型。</p><p>量子数据模型表现出量子叠加和量子纠缠的特性，导致联合概率分布，这可能需要成倍数量的经典计算资源来表示或存储。能够在量子处理器 / 传感器 / 网络上生成 / 模拟的量子数据包括化学物质和量子物质的模拟、量子控制、量子通信网络、量子计算学等。</p><p>但一个不容忽视的问题是，NISQ 处理器生成的量子数据是嘈杂的，通常在测量发生之前就被纠缠了。但是，将量子机器学习应用于嘈杂的纠缠量子数据上，可以最大程度地提取有用的经典信息。基于这种技术的启发，TFQ 库为模型的开发提供了基元（该模型可分解和概括量子数据中的关联），从而为改进现有量子算法或发现新的量子算法提供了可能。</p><p>引入的第二个概念是混合量子经典模型。由于近期的量子处理器仍然很小且嘈杂，因此量子模型不能单独使用量子处理器，NISQ 处理器需要与经典处理器协同工作才有效。由于 TensorFlow 已经支持跨 CPU、GPU 和 TPU 的异构计算，因此它是试验混合量子经典算法的天然平台。</p><p>TFQ 包含了特定量子计算所需的基本结构，例如量子比特、门、电路和测量运算符。用户指定的量子计算然后可以在模拟或真实硬件上执行。Cirq 也包含了大量机器，可帮助用户为 NISQ 机器（例如编译器和调度程序）设计出高效算法，并且能使混合量子经典算法的实现在量子电路模拟器上运行，并最终在量子处理器上运行。</p><p><img src=\"https://static001.infoq.cn/resource/image/4f/ab/4f344d532aabda29f8f7858526fe58ab.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>如今，谷歌已经将 TFQ 应用到了混合量子经典卷积神经网络、量子控制的机器学习、量子神经网络的分层学习、量子动态学习、混合量子态的生成建模以及通过经典递归神经网络来学习量子神经网络等方面。以及通过经典循环神经网络，来学习量子神经网络等等方面。</p><p>谷歌团队在 TFQ 白皮书中放出了这些量子应用的示例，并能在浏览器中通过 Colab 运行（项目地址：&nbsp;https://github.com/tensorflow/quantum/tree/research）。</p><p>TFQ 的工作原理</p><p>TFQ 能够帮助研究人员在单个计算图中构建量子数据集、量子模型和作为张量的经典控制参数。导致经典概率事件的量子测量结果可通过 TensorFlow Ops 获得，可用标准 Keras 函数进行训练。</p><p>为直观地阐述如何使用量子数据，可以考虑使用量子神经网络对量子状态进行监督分类。正如经典 ML 一样，量子 ML 面临的关键问题是如何对&ldquo;噪音数据&rdquo;进行分类。为了建立和训练这种模型，研究人员需要做的工作有：</p><ul><li>准备一个量子数据集</li></ul><p>量子数据被加载为张量（数字的多维数组）。每个量子数据张量都指定为用 Cirq 编写的量子电路，该电路可实时生成量子数据。张量由 TensorFlow 在量子计算机上执行以生成量子数据集。</p><ul><li>评估量子神经网络模型</li></ul><p>研究人员可以使用 Cirq 设计量子神经网络原型，然后将其嵌入 TensorFlow 计算图中。基于对量子数据结构的认知，可以从几大类中选择参数化的量子模型。该模型的目标是执行量子处理，以提取隐藏在典型纠缠状态下的信息。换言之，量子模型本质上是对输入的量子数据进行分离，将隐藏的信息编码在经典关联中，从而使其可用于局部测量和经典后处理（算法）。</p><ul><li>样本或平均值</li></ul><p>量子态的测量从经典随机变量中，以样本形式提取经典信息。来自该随机变量值的分布通常取决于量子态本身以及所测得的可观测值。由于许多变分算法依赖于测量值的平均值，因此 TFQ 提供了在涉及步骤（1）和（2）的多个运行中求平均值的方法。</p><ul><li>评估经典神经网络模型</li></ul><p>提取经典信息后，其格式适用于进一步经典后处理。由于所提取的信息仍然可能被编码为测量期望之间的经典关联，因此可以使用经典的深度神经网络来提取这种关联。</p><ul><li>评估成本函数</li></ul><p>根据经典后处理的结果，评估成本函数。这可以基于模型执行分类任务的准确性 (如果量子数据被标记)，或者基于其他标准 (如果任务是无监督的)。</p><ul><li>评估梯度和更新参数</li></ul><p>评估成本函数后，应更新管道中的自由参数，这通常是通过梯度下降执行的。</p><p><img src=\"https://static001.infoq.cn/resource/image/26/30/2644ae448b4996d115ad518278f9c130.png\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>TensorFlow Quantum 的关键特征是它能够同时训练和执行多个量子电路。TensorFlow 能够跨计算机集群进行并行计算，并能够在多核计算机上模拟相对较大的量子电路。为了实现后者，谷歌还宣布发布新的高性能开源量子电路模拟器 qsim (项目地址：&nbsp;https://github.com/quantumlib/qsim&nbsp;)，该模拟器已证明能够在 111 秒内模拟门深度为 14 的 32 比特量子电路。</p><p>接下来，谷歌列举了具体的示例来帮助开发者理解。</p><p>基础知识</p><p>安装 TensorFlow Quantum：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>pip install -q tensorflow-quantum</p></td></tr></tbody></table><p><br></p><p>导入 TensorFlow 和模块依赖项：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>import tensorflow as tf</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>import tensorflow_quantum as tfq</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>import cirq</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>import sympy</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>import numpy as np</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p># visualization tools</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>%matplotlib inline</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>import matplotlib.pyplot as plt</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>from cirq.contrib.svg import SVGCircuit</p></td></tr></tbody></table><p><br></p><p>参数化量子电路</p><p>现在，使用 cirq 来模拟量子电路。Cirq 是谷歌提供的用于量子计算的 Python 库，可以使用它来定义电路，包括静态和参数化门。Cirq 使用 SymPy 符号表示自由参数：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>a, b = sympy.symbols(&#39;a b&#39;)</p></td></tr></tbody></table><p><br></p><p>以下代码使用参数创建一个两比特电路：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># Create two qubits</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>q0, q1 = cirq.GridQubit.rect(1, 2)</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p># Create a circuit on these qubits using the parameters you created above.</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>circuit = cirq.Circuit(</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; cirq.rx(a).on(q0),</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; cirq.ry(b).on(q1), cirq.CNOT(control=q0, target=q1))</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>SVGCircuit(circuit)</p></td></tr></tbody></table><p><br></p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>findfont: Font family [&#39;Arial&#39;] not found. Falling back to DejaVu Sans.</p></td></tr></tbody></table><p><br></p><p><img src=\"https://static001.infoq.cn/resource/image/33/ec/339cd362f56bba904256df7b479721ec.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>要评估电路，可以使用该 cirq.Simulator，可以通过传入 cirq.ParamResolver 对象来用特定编号替换电路中的自由参数。以下代码计算参数化电路的原始状态向量输出：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># Calculate a state vector with a=0.5 and b=-0.5.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>resolver = cirq.ParamResolver({a: 0.5, b: -0.5})</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>output_state_vector = cirq.Simulator().simulate(circuit, resolver).final_state</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>output_state_vector</p></td></tr></tbody></table><p><br></p><p>我们得到具有 4 个元素的状态向量：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>数组（[0.9387913 + 0.j，-0.23971277 + 0.j，</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; 0. + 0.06120872j，0. -0.23971277j]，dtype = complex64）</p></td></tr></tbody></table><p><br></p><p>通常，我们不能直接从量子计算机获得状态向量。因此，我们通常不尝试读取状态向量，而是尝试计算与状态向量间接相关的特定值。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>z0 = cirq.Z（q0）</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>qubit_map = {q0：0，q1：1}&nbsp;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>z0.expectation_from_wavefunction（output_state_vector，qubit_map）.real</p></td></tr></tbody></table><p><br></p><p>这是 Z 运算符的期望值。通常，我们在布洛球的 Z 轴上获得量子计算结果。通过获取样本，您可以轻松估算 pauli Z 运算符的期望值。</p><p><img src=\"https://static001.infoq.cn/resource/image/74/d7/74b0f24b32732046c7613edde58c46d7.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>量子态表示为布洛球</p><p>结果是：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>0.8775825500488281</p></td></tr></tbody></table><p><br></p><p>如果：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>z0x1 = 0.5 * z0 + cirq.X(q1)</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>z0x1.expectation_from_wavefunction(output_state_vector, qubit_map).real</p></td></tr></tbody></table><p><br></p><p>结果为：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>-0.04063427448272705</p></td></tr></tbody></table><p><br></p><p>量子电路作为张量</p><p>TensorFlow Quantum（TFQ）提供 tfq.convert_to_tensor 将 Cirq 对象转换为张量，开发者可以将 Cirq 对象发送到谷歌的 quantum layers 和 quantum ops，可以在 Cirq 电路和 Cirq Paulis 的列表或数组上调用该函数：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># Rank 1 tensor containing 1 circuit.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>circuit_tensor = tfq.convert_to_tensor([circuit])</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>print(circuit_tensor.shape)</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>print(circuit_tensor.dtype)</p></td></tr></tbody></table><p><br></p><p>结果如下：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>(1,)</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&lt;dtype: &#39;string&#39;&gt;</p></td></tr></tbody></table><p><br></p><p>这会将 Cirq 对象编码为 tf.string 张量，tfq 操作可根据需要对其进行解码：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># Rank 1 tensor containing 2 Pauli operators.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>pauli_tensor = tfq.convert_to_tensor([z0, z0x1])</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>pauli_tensor.shape</p></td></tr></tbody></table><p><br></p><p>结果为：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>TensorShape（[2]）</p></td></tr></tbody></table><p><br></p><p>量子经典混合</p><p>现在，开发者已经了解部分基础知识，让我们使用 TensorFlow Quantum 构建一个混合的量子经典神经网络，将训练经典的神经网络来控制单个量子位。将优化控制以正确准备处于 0 或 1 状态的量子位，从而克服模拟的系统校准误差。该图显示了体系结构：</p><p><img src=\"https://static001.infoq.cn/resource/image/4f/21/4f4a528f9f469fbf2180f507ab831521.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>即使没有神经网络，这也是一个直接解决的问题，但主题类似于可能使用 TFQ 解决的实际量子控制问题，演示了使用 tfq.layers.ControlledPQC 内部的（参数化量子电路）层进行量子经典计算的端到端示例 tf.keras.Model。</p><p>第一步：受控电路定义</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># Parameters that the classical NN will feed values into.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>control_params = sympy.symbols(&#39;theta_1 theta_2 theta_3&#39;)</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p># Create the parameterized circuit.</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>qubit = cirq.GridQubit(0, 0)</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>model_circuit = cirq.Circuit(</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; cirq.rz(control_params[0])(qubit),</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; &nbsp; cirq.ry(control_params[1])(qubit),</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; cirq.rx(control_params[2])(qubit))</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>SVGCircuit(model_circuit)</p></td></tr></tbody></table><p><br></p><p><img src=\"https://static001.infoq.cn/resource/image/52/27/52d6a0a40a5ed993f90931db3eacb427.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>第二步：定义控制器网络</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># The classical neural network layers.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>controller = tf.keras.Sequential([</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; tf.keras.layers.Dense(10, activation=&#39;elu&#39;),</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; &nbsp; tf.keras.layers.Dense(3)</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>])</p></td></tr></tbody></table><p><br></p><p>给定一批命令，控制器为受控电路输出一批控制信号。<br>控制器是随机初始化的，因此，这些输出尚无用。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>controller(tf.constant([[0.0],[1.0]])).numpy()</p></td></tr></tbody></table><p><br></p><p>结果为：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>数组（[[0.，0.，0.]，</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp;[-0.16003934，0.26334327，0.3790441]]，dtype = float32）</p></td></tr></tbody></table><p><br></p><p>第三步：将控制器连接到电路</p><p>使用 tfq 将控制器连接到被控制电路，作为单一的 keras.Model。首先，定义模型的输入：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># This input is the simulated miscalibration that the model will learn to correct.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>circuits_input = tf.keras.Input(shape=(),</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # The circuit-tensor has dtype `tf.string`&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dtype=tf.string,</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name=&#39;circuits_input&#39;)</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p># Commands will be either `0` or `1`, specifying the state to set the qubit to.</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>commands_input = tf.keras.Input(shape=(1,),</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dtype=tf.dtypes.float32,</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name=&#39;commands_input&#39;)</p></td></tr></tbody></table><p><br></p><p>接下来，将运算应用于这些输入，以定义计算：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>dense_2 = controller(commands_input)</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p># TFQ layer for classically controlled circuits.</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>expectation_layer = tfq.layers.ControlledPQC(model_circuit,</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# Observe Z</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;operators = cirq.Z(qubit))</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>expectation = expectation_layer([circuits_input, dense_2])</p></td></tr></tbody></table><p><br></p><p>现在将此计算打包为 tf.keras.Model：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># The full Keras model is built from our layers.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>model = tf.keras.Model(inputs=[circuits_input, commands_input],</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;outputs=expectation)</p></td></tr></tbody></table><p><br></p><p>注意：可能需要系统安装 graphviz 软件包。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)</p></td></tr></tbody></table><p><br></p><p>结果为：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>无法导入 pydot。您必须安装 pydot 和 graphviz 才能使 pydotprint 运行。</p></td></tr></tbody></table><p><br></p><p>数据集</p><p>模型尝试为每个命令输出正确的正确测量值 &nbsp;hatZ hatZ，命令和正确值定义如下：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p># The command input values to the classical NN.</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>commands = np.array([[0], [1]], dtype=np.float32)</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p># The desired Z expectation value at output of quantum circuit.</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>expected_outputs = np.array([[1], [-1]], dtype=np.float32)</p></td></tr></tbody></table><p><br></p><p>这不是此任务的整个训练数据集。数据集中的每个数据点也需要一个输入电路。</p><p>输入点定义</p><p>下面的输入电路定义了模型将学习校正的随机失调：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>random_rotations = np.random.uniform(0, 2 * np.pi, 3)</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>noisy_preparation = cirq.Circuit(</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; cirq.rx(random_rotations[0])(qubit),</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; cirq.ry(random_rotations[1])(qubit),</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; cirq.rz(random_rotations[2])(qubit)</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>)</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>datapoint_circuits = tfq.convert_to_tensor([</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; noisy_preparation</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>] * 2) &nbsp;# Make two copied of this circuit</p></td></tr></tbody></table><p><br></p><p>电路有两个副本，每个数据点一个。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>datapoint_circuits.shape</p></td></tr></tbody></table><p><br></p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>TensorShape（[2]）</p></td></tr></tbody></table><p><br></p><p>训练</p><p>使用定义的输入测试 tfq 模型：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>model([datapoint_circuits, commands]).numpy()</p></td></tr></tbody></table><p><br></p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>数组（[[0.16693401]，</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp;[-0.17766671]]，dtype = float32）</p></td></tr></tbody></table><p><br></p><p>现在，运行标准的训练过程将这些值调整为 expected_outputs。</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>loss = tf.keras.losses.MeanSquaredError()</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>model.compile(optimizer=optimizer, loss=loss)</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>history = model.fit(x=[datapoint_circuits, commands],</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; y=expected_outputs,</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; epochs=30,</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; verbose=0)</p></td></tr></tbody></table><p><br></p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>plt.plot(history.history[&#39;loss&#39;])</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>plt.title(&quot;Learning to Control a Qubit&quot;)</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>plt.xlabel(&quot;Iterations&quot;)</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>plt.ylabel(&quot;Error in Control&quot;)</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>plt.show()</p></td></tr></tbody></table><p><br></p><p><img src=\"https://static001.infoq.cn/resource/image/a9/1f/a9cf9674569851511ffb32b2ed90891f.jpg\" alt=\"谷歌开源用于训练量子模型的机器学习框架TensorFlow Quantum\" class=\"fr-fic fr-dii\"></p><p>从该图中可以看出，神经网络已经学会克服系统失调。</p><p>验证输出</p><p>现在，使用经过训练的模型来校正量子位校准误差。使用 Cirq：</p><p>现在，使用经过训练的模型来校正量子位校准误差。使用 Cirq：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>def check_error(command_values, desired_values):</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>&nbsp; &quot;&quot;&quot;Based on the value in `command_value` see how well you could prepare</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; the full circuit to have `desired_value` when taking expectation w.r.t. Z.&quot;&quot;&quot;</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp; params_to_prepare_output = controller(command_values).numpy()</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>&nbsp; full_circuit = noisy_preparation + model_circuit</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>&nbsp; # Test how well you can prepare a state to get expectation the expectation</p></td></tr><tr><td><div data-line-number=\"8\"><br></div></td><td><p>&nbsp; # value in `desired_values`</p></td></tr><tr><td><div data-line-number=\"9\"><br></div></td><td><p>&nbsp; for index in [0, 1]:</p></td></tr><tr><td><div data-line-number=\"10\"><br></div></td><td><p>&nbsp; &nbsp; state = cirq_simulator.simulate(</p></td></tr><tr><td><div data-line-number=\"11\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; full_circuit,</p></td></tr><tr><td><div data-line-number=\"12\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; {s:v for (s,v) in zip(control_params, params_to_prepare_output[index])}</p></td></tr><tr><td><div data-line-number=\"13\"><br></div></td><td><p>&nbsp; &nbsp; ).final_state</p></td></tr><tr><td><div data-line-number=\"14\"><br></div></td><td><p>&nbsp; &nbsp; expectation = z0.expectation_from_wavefunction(state, {qubit: 0}).real</p></td></tr><tr><td><div data-line-number=\"15\"><br></div></td><td><p>&nbsp; &nbsp; print(f&#39;For a desired output (expectation) of {desired_values[index]} with&#39;</p></td></tr><tr><td><div data-line-number=\"16\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f&#39; noisy preparation, the controller\\nnetwork found the following &#39;</p></td></tr><tr><td><div data-line-number=\"17\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f&#39;values for theta: {params_to_prepare_output[index]}\\nWhich gives an&#39;</p></td></tr><tr><td><div data-line-number=\"18\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f&#39; actual expectation of: {expectation}\\n&#39;)</p></td></tr><tr><td><div data-line-number=\"19\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"20\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"21\"><br></div></td><td><p>check_error(commands, expected_outputs)</p></td></tr></tbody></table><p><br></p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>For a desired output (expectation) of [1.] with noisy preparation, the controller</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>network found the following values for theta: [-0.22817115 -0.2512403 &nbsp;-1.5496594 ]</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>Which gives an actual expectation of: 0.9663878679275513</p></td></tr><tr><td><div data-line-number=\"4\"><br></div></td><td><p>&nbsp;</p></td></tr><tr><td><div data-line-number=\"5\"><br></div></td><td><p>For a desired output (expectation) of [-1.] with noisy preparation, the controller</p></td></tr><tr><td><div data-line-number=\"6\"><br></div></td><td><p>network found the following values for theta: [-1.2500848 &nbsp;1.4088008 &nbsp;2.5982447]</p></td></tr><tr><td><div data-line-number=\"7\"><br></div></td><td><p>Which gives an actual expectation of: -0.9780153036117554</p></td></tr></tbody></table><p><br></p><p>训练期间损失函数的值提供了关于模型学习程度的粗略概念。损耗越小，上述单元格中的期望值越接近 desired_values。如果不关心参数值，则可以始终使用 tfq 命令检查上面的输出：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>model([datapoint_circuits, commands])</p></td></tr></tbody></table><p><br></p><p>结果为：</p><p>复制代码</p><p><br></p><p><br></p><table><tbody><tr><td><div data-line-number=\"1\"><br></div></td><td><p>&lt;tf.Tensor: shape=(2, 1), dtype=float32, numpy=</p></td></tr><tr><td><div data-line-number=\"2\"><br></div></td><td><p>array([[ 0.96638787],</p></td></tr><tr><td><div data-line-number=\"3\"><br></div></td><td><p>&nbsp; &nbsp; &nbsp; &nbsp;[-0.9780154 ]], dtype=float32)&gt;</p></td></tr></tbody></table><p><br></p><p>如果想了解更多教程，可以访问 Tensorflow 网站。</p>', 0, '物理学家理查德&middot; 费曼曾说过：自然不是古板的，如果想要要模拟自然，最好使用量子力学定律。2020 年 3 月 11 日，TensorFlow Dev Summit 峰会通过线上直播的方式', NULL, '谷歌开源用于训练量子模型的机器学习框架 TensorFlow Quantum', 2, 16, 0, '2020-03-12 18:21:57', 0, '2020-04-04 11:17:34', 'admin', 'admin', 0, 'post', 0, 1);
INSERT INTO `post` VALUES (22, NULL, '<p>2020 年 3 月 9 日，腾讯公司正式对外发布了《腾讯研发大数据报告》，该报告首次披露了腾讯集团在 2019 年在产品及研发方面的关键数据。报告显示，研发人员在集团员工的占比已超过 66%，2019 年全年新增 12.9 亿行代码，C++ 是腾讯使用最多编程语言，Go 语言首次跻身前五。</p><p>本次公布的报告数据来源于支撑腾讯全业务研发流程的三大平台：敏捷研发协作平台 TAPD、腾讯代码托管平台工蜂与腾讯集成交付系统腾讯 CI。</p><p>在研发投入和研发效能方面，数据可圈可点：</p><ul><li>研发人员占比达 66%，位居互联网公司前列；</li><li>新增研发项目 3500 个，其中 To B 项目增长率达 77%；</li><li>新增代码行数 12.9 亿，同比增长 30%；</li><li>60% 的项目团队规模在 20 人以下，47.8% 的项目迭代周期在 1 周之内；</li><li>内部使用最多的五种编程语言分别是：C++、JavaScript、Go、Java、Python。</li><li>&hellip;&hellip;</li></ul><p>而在开源方面，腾讯 2019 年同样取得了不俗的成绩：</p><ul><li>在内部开源方面，腾讯内部整体代码开源率由 2019 年年初的 20% 增长至年底的 70%；</li><li>在外部开源方面，截至 2019 年年底，腾讯在 Github 自主开源项目数为 92 个，贡献者超过 1000 个，获得 Star 数超过 27 万，在 Github 全球公司贡献榜上的排名前十。</li></ul><p>2019 年全年，腾讯开源超过 22 个项目，包括 TubeMQ 亿万级分布式消息中间件、TencentOS tiny 自研轻量级物联网操作系统、TKE 腾讯云容器服务和 TBase 企业级分布式 HTAP 数据库管理系统。</p><p>除此以外，腾讯于开源社区也建立了良好的合作关系。Angel 项目完成了从单一的模型训练平台到全栈机器学习平台的技术演进，成功从 LF AI 基金会毕业；TARS 项目成为开源社区仅有支持五种开发语言并有完善服务治理和运营平台的微服务开发框架。此外，腾讯还作为创始会员，加入了 LF Edge 基金会，和业界合作伙伴一起推动边缘计算软硬件开源技术的发展。</p><p>自 2019 年 1 月 4 日，腾讯正式成立技术委员会以来，可以看到腾讯在自研上云和开源协同等方面都取得了不错的成绩，腾讯的研发文化与技术品牌也体现出了焕然一新的面貌。随着全面云计算时代的来临，一切与云相关的技术将成为未来的趋势与战略控制点，不管是互联网企业还是传统企业，都应该拥抱云、拥抱变化，保持自我革新，迎接无限的挑战。</p>', 0, '2020 年 3 月 9 日，腾讯公司正式对外发布了《腾讯研发大数据报告》，该报告首次披露了腾讯集团在 2019 年在产品及研发方面的关键数据。报告显示，研发人员在集团员工的占比已超过 66%，201', NULL, '腾讯首次公布研发大数据：研发人员占比 66%，C++ 是最常用语言', 4, 9, 2, '2020-03-12 18:23:16', 0, '2020-03-12 22:33:06', 'admin', 'admin', 0, 'post', 0, 0);
INSERT INTO `post` VALUES (23, NULL, '<p>Druid 是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统，旨在快速处理大规模的数据，并能够实现快速查询和分析。尤其是当发生代码部署、机器故障以及其他产品系统遇到宕机等情况时，Druid 仍能够保持 100% 正常运行。</p><p>Apache Druid 社区在今年初发布了&nbsp;Druid 0.17.0&nbsp;。这是该项目自 Apache Incubator 毕业以来的第一个版本，因此也是一个重要的里程碑。</p><p>Twitter 在一年多之前就开始采用 Druid，并应用到多个场景中。对于 Twitter 的实践经验，我们采访了 Twitter 大数据引擎负责人罗震霄。他将在 QCon 全球软件开发大会（北京站）2020 分享主题为《&nbsp;Twitter ZB 级实时数据分析实践》的演讲。</p><p>罗震霄，Twitter Sr. Staff Engineer，负责 Twitter 大数据引擎的开发与运营，主要负责项目 Druid、Presto、Spark、Hive。在加入 Twitter 之前，在 Uber、Netflix、Facebook 从事大数据相关的研发与管理工作。是 Presto Foundation committer 和技术委员会委员。他于复旦大学获得学士学位，并于 University of Wisconsin Madison 获得硕士学位。</p><p>InfoQ：您好，请问 Druid 自开源以来，有哪些重要的演化？</p><p>罗震霄：Twitter 用 Druid 已经有一年多了，从 0.15 到 0.17，这几个版本都比较稳定，主要的变化有：</p><ul><li>设计并实现了 Native Ingestion，彻底摆脱了对 MapReduce 的依赖，可以直接 Ingest Parquet，ORC 文件。</li><li>Data Ingestion 设计了 Indexer Process。</li><li>SQL 功能持续加强。</li><li>方便易用的用户界面。</li><li>更全面的 Security and Privacy Support，例如 LDAP Authentication and Authorization。</li></ul><p>InfoQ：一般的企业，哪些场景需要使用 Druid？Twitter 为什么选择了 Druid？Twitter 里有哪些 Druid 的应用举例？</p><p>罗震霄：Druid 的主要特点是性能快，规模大，易用性好。</p><ul><li>性能可以达到 1 秒以下，真正实现了大数据的实时性计算；</li><li>Druid 单群可以扩展至 2000 台节点以上，很好的满足了企业对大数据规模的要求；</li><li>Druid 的用户界面非常友好，用户常常可以通过拖拖拽拽进行查询，方便非技术类人员的应用。</li></ul><p>Twitter 业务有很多实时的大数据需求，我们当时对比了市场上现有的开源方案，在稳定性、扩展性、性能和易用性方面 Druid 都有一定优势，最后选择 Druid。目前来看公司上下对 Druid 还比较满意。</p><p>Twitter 所有的 user events 都通过内部 Message Queue 导入 Druid，用户可以实时对 Druid 进行查询，也可以在 Druid 基础上设置各类的监控和提醒。实时监控很好的利用了 Druid 性能快和方便查询的特性。</p><p>Twitter 内部用 Druid 很多，有三大类：其一，将用户行为数据导入 Druid，进行实时用户行为分析；其二，通过 Druid 为广告商提供实时的广告效果分析；其三，利用 Druid 的实时特性，做实时监控和实时报表。</p><p><br></p><p>InfoQ：比如现在某个企业已经有了成熟的 Lamda 架构的离线实时系统。而 Druid 也是 Lamda 架构的，那哪些部分是可以相互 Share 的吗？</p><p>罗震霄：Druid 需要自己的存储，可以将 Message Queue 同时导入离线系统和实时系统（Druid）。</p><p>如果对一些数据经过滤后有实时的计算需求，也可以通过 Hadoop Distributed File System 将数据导入 Druid。</p><p>一般来说，企业的 Message Queue 可以共享，Hadoop Distributed File System 也可以作为 Druid 的数据源进行共享。</p><p>Twitter 现在正在开发 Presto Druid Connector，通过 Presto 实时查询 Druid 数据，这样企业内部的查询语言，查询系统也可以共享。</p><p>因为 Twitter 数据分析大多数是通过 SQL 进行的。公司上下对 SQL on Druid 的需求很高。</p><p>现有的 DruidSQL 功能不太完善，SubQuery、Join 都不支持。如果将 Druid 数据再导入其他存储系统，不仅费时费力，而且很难保证数据的一致性。基于这些考虑，我们决定开发 Presto Druid Connector，用 Presto 对 Druid 数据提供完整的 SQL 分析，避免了数据重复，节省了存储空间，而且还可以利用 Presto&nbsp;Connector 的优势，跨平台进行 Join 操作，比如，Join Hadoop Data with Druid Data。</p><p>现在有一些用户在使用 Presto Druid Connector，大家还是比较满意的。下一步，我们会进一步提升性能，pushdown 各类子运算到 Druid。最终的愿景是争取将所有的 SQL 操作统一到 Presto 上来。</p><p><br></p><p>InfoQ：如果一个系统每天需要处理 PB 级别的系统，再增加一个 Druid 这样的存储系统，是不是很大程度上增加了存储空间的压力？原系统和 Druid，最好的融合方式是什么样的？</p><p>罗震霄：为了性能的提升，Druid 需要自己的存储。对 Twitter 而言，查询速度，和系统的易用性，较之存储空间，更为重要。</p><p>Druid 需要做自己的存储的原因是：</p><p>Druid 一个重要的设计目标是实时查询，也就是查询延迟一定要在 1 秒以下。</p><p>现有硬件的基本性能决定，从内存中顺序读取 4GB 数据的延时在 1 秒左右，实际应用场景中，我们处理的数据远远大于 4GB，所以，没有索引，仅仅靠扫描文件，是不可能达到实时查询要求的。</p><p>基于这些考虑，Druid 设计并实现了自己的存储格式，主要是列式存储，并应用字典、RLE 等方式进行优化。Druid 还设计了 bitmap 索引。在实际应用中，bitmap 索引会以 mmap 方式放在内存中。绝大多数查询可以通过 bitmap 索引直接找到结果，少数查询会通过列式存储进行有选择性的文件查询。</p><p>我们也在开发一些项目，尽量减少不必要的数据重复。比如 Presto Druid Connector，通过 Presto 实时查询 Druid 数据，这样当用户需要对 Druid 数据进行 SQL 分析时，用 Presto 就可以了，不必要拷贝数据。</p><p><br></p><p>InfoQ：Druid 和 Kylin 这样的开源软件主要差别在哪里？</p><p>罗震霄：对 Kylin 实在不太熟悉，不好妄加评论。</p><p>我们当时主要考虑的是 Druid 和 Pinot，这两个现在应用比较广泛。相对而言，Druid 更稳定一些，社区也更大一些。</p><p><br></p><p>InfoQ：目前还有哪些 OLAP 平台可供选择？Druid 相对有何优势？</p><p>罗震霄：现有的 OLAP 平台比较多，开源方面，有 Hive、 Impala、Spark、Presto、Drill、Druid、Pinot 等等。</p><p>Twitter 现在的技术布局：对实时计算，统一于 Druid，对秒级到几十分钟级的数据分析，统一于 Presto，对小时级别的 ETL，统一于 Spark。</p><p>Druid 主要的优势在性能，稳定性，扩展性，和易用性。能够达到 1 秒以下的实时引擎，只有 Druid 和 Pinot，如果考虑到扩展性和稳定性，Druid 的优势比较明显。</p><p><br></p><p>InfoQ：在 Twitter 里，您们定制了关于 Druid 的哪些开发功能？（与开源版本不同的功能）</p><p>罗震霄：主要是四个方面：</p><ul><li>对 Thrift 文件格式的支持，Twitter 内部有大量的 Thrift 文件。</li><li>Presto Druid Connector，通过 Presto 对 Druid 数据进行 SQL 分析。</li><li>Multi-tenancy Druid，Druid 支持不同的用户类型，包括数据安全，隐私保护，和数据隔离。</li><li>Native Ingestion，摆脱对 MapReduce 的依赖，直接导入 Parquet 文件。</li></ul><p>InfoQ：未来，Twitter 还计划对 Druid 开发或加强哪些功能？</p><p>罗震霄：主要有以下计划：</p><ul><li>Unified Indexing Service，为用户提供方便的一键式导入。</li><li>Secure Druid，完整的 Druid 信息安全，包括 LDAP Authentication and Authorization。</li><li>Pushdown for Presto Druid Connector，用 Presto 为 Druid 提供完整的 SQL 分析，并充分发挥 Druid 的性能优势，包括 Predicate Pushdown， Aggregation Pushdown，Limit Pushdown。</li></ul>', 0, 'Druid 是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统，旨在快速处理大规模的数据，并能够实现快速查询和分析。尤其是当发生代码部署、机器故障以及其他产品系统遇到宕机等情况时，Druid', NULL, 'Twitter 如何应用 Druid 分析 ZB 级实时数据？', 4, 9, 0, '2020-03-12 18:24:02', 0, '2020-03-12 22:28:31', 'admin', 'admin', 0, 'post', 0, 1);
INSERT INTO `post` VALUES (24, NULL, '<p>根据外媒报道，莱斯大学的计算机科学家们已经克服了人工智能产业迅速发展的一个主要障碍，他们证明了在不依赖于图形处理单元（GPU）等专业级加速硬件的情况下，也能够实现对深度学习技术的加速。这个名为 SLIDE 的算法是第一个在 CPU 上比 GPU 更快地训练深度神经网络的算法。</p><p>在奥斯汀召开的 2020 机器学习系统会议&nbsp;MLSys&nbsp;上，来自莱斯大学的计算机科学家们，在来自英特尔公司的合作伙伴的支持下，于 3 月 2 日在奥斯汀会议中心展示了他们的最新研究成果。</p><p>当下，为了实现深度学习，许多公司正大力投资于图形处理单元（GPU）和其他专业级硬件。深度学习是一种强大的人工智能，如今亚马逊 Alexa 和苹果 Siri 等智能助理、面部识别、产品推荐系统和其他技术都有深度学习在背后作为支撑。深度学习炙手可热的程度可以举一例说明，作为一手打造该行业金字招牌&ldquo;特斯拉 V100 Tensor Core GPU&rdquo;芯片的制造商，Nvidia 公司最近的财务报告显示，其 2019 第四季度收入同比增长了 41%。</p><p>而莱斯大学的研究人员创造了一种可替代 GPU 的节省成本的算法，称为&ldquo;次线性深度学习引擎&rdquo;（sub-linear deep learning engine，简称 SLIDE），这种算法只需使用一般通用的中央处理器（CPU），而无需专业级的加速硬件。</p><p>&ldquo;我们的测试表明，SLIDE 是第一个基于 CPU 实现的深度学习智能算法，它的性能可以超越那些依照产业规模的建议采用大型全连接架构使用 GPU 硬件加速来实现数据集的方法&rdquo;，&nbsp;Anshumali Shrivastava&nbsp;这样说。这位莱斯大学布朗工程学院的助理教授与研究生&nbsp;Beidi Chen&nbsp;和&nbsp;Tharun Medini&nbsp;一起开发了该 SLIDE 算法。</p><p>SLIDE 不需要依赖于 GPU，因为这种算法从根本上采用了一种完全不同的深度学习方法。深度神经网络训练技术标准的&ldquo;反向传播&rdquo;算法需要矩阵乘法，如此繁重的计算量正是适合 GPU 发挥性能的理想场所。然而，通过 SLIDE 算法，Shrivastava、Chen 和 Medini 把神经网络训练转变成为一个可以用哈希表来解决的搜索问题。</p><p>与反向传播训练技术相比，这种 SLIDE 算法可以从根本上减少大量的计算开销。Shrivastava 举例说，如今诸如亚马逊、谷歌以及其他公司使用 GPU 打造的基于云的深度学习服务的顶级平台，一般会使用 8 块&ldquo;特斯拉 V100&rdquo;芯片，其费用约为 10 万美元。</p><p>莱斯大学计算机科学研究生 Beidi Chen 和 Tharun Medini 参与开发了 SLIDE，这是一种无需依赖图形处理单元对深度神经网络进行训练的算法。（图片来源：Jeff Fitlow/ 莱斯大学）</p><p>&ldquo;我们有一个在实验室运行的测试用例，它完全能承载一块 V100 芯片的工作负荷，也即是一个适用于 GPU 内存的，运行在大型全连接网络中有超过 1 亿个参数的计算量&rdquo;，Shrivastava 说，&ldquo;我们用最先进的谷歌的 TensorFlow 软件包来训练该算法，它只花了 3 个半小时就完成了训练。&rdquo;</p><p>&ldquo;我们随后证明，我们的新算法甚至可以在一小时内完成该训练，而且并不是运行在 GPU 上，而是运行在 44 核的 xeon-class CPU 上，&rdquo; Shrivastava 说。</p><p>深度学习网络的灵感来自生物学，其核心特征是人工神经元，这些神经元是一小段可以学习并执行特定的任务计算机代码。一个深度学习网络可能包含数百万甚至数十亿这种人工神经元，只要通过对海量数据的学习，这些神经元共同工作就有可能学习并做出与人类水平相当的专家决策。例如，如果一个深度神经网络被训练来识别照片中的物体，当识别一张猫的照片或是识别一辆校车时，它将使用不同的神经元来进行学习。</p><p>&ldquo;你不需要对每个用例的所有神经元都进行训练，&rdquo; Medini 解释说，&ldquo;我们是这样想的，&lsquo;如果我们只去挑选出相关的神经元，那这就变成了一个搜索问题。&rsquo; 因此，从算法上讲，我们的想法就是使用局部敏感哈希算法来避免矩阵乘法的复杂性。&rdquo;</p><p>哈希算法是 20 世纪 90 年代为互联网搜索发明的一种数据索引方法。它使用数字方法将大量信息，例如整个网站所有网页或一本书的所有章节，编码为一串称为哈希散列的数字。哈希表就是记录这些哈希散列值并可以实现快速搜索的列表。</p><p>&ldquo;在 TensorFlow 或 PyTorch 上实现我们的算法是毫无意义的，因为这些软件执行的第一件事就是不管三七二十一先把你正在做的事情转换成一个矩阵乘法问题，&rdquo; Chen 说。&ldquo;而这正是我们的算法想要避免的。所以我们是从零开始写我们自己的 C++ 代码的。&rdquo;</p><p>Shrivastava 说，SLIDE 相对于反向传播的最大优势在于它采用了数据并行的方式。</p><p>&ldquo;我的意思是，通过数据并行，如果我想要训练两个数据实例，比方说一个是一只猫的形象，另一个是公共汽车，他们可能会激活不同的神经元，该 SLIDE 算法可以对这两个实例分别独立地进行更新或训练，&rdquo; 他说，&ldquo;这就大大地提高了 CPU 并行性的利用率。&rdquo;</p><p>&ldquo;另一方面，与 GPU 相比，我们需要更大的存储空间，&rdquo; 他说，&ldquo;在主存储器中有一个缓存层次结构，如果你使用时不够小心，可能会遇到一个叫做内存颠簸（cache thrashing）的问题，那样就会发生大量缺页中断。&rdquo;</p><p>Shrivastava 说，他的团队第一次使用 SLIDE 进行实验时，就发生了严重的内存颠簸，但他们的训练时间仍然与 GPU 的训练时间相当，甚至更快。于是，他、Chen 和 Medini 于 2019 年 3 月在 arXiv 上发布了初步实验结果，并将他们的代码上传到 GitHub。几周后，英特尔公司主动联系了他们。</p><p>&ldquo;来自英特尔的合作伙伴注意到了我们实验中的缓存问题，&rdquo; 他说，&ldquo;他们告诉我们，他们可以与我们进行合作，让这个算法更快地完成训练，之后的事实证明他们是正确的。在他们的帮助下，我们的实验性能又提高了约 50%。&rdquo;</p><p>Shrivastava 说，SLIDE 还远远未达到其最大潜力。</p><p>&ldquo;我们只能算是初尝甜头而已，&rdquo; 他说，&ldquo;我们还可以做很多事情来对这个算法进行优化。例如，我们还没有使用矢量化，也没有在 CPU 中使用内置的加速器，比如 Intel Deep Learning Boost 技术。我们还有很多其他的技巧可以让这个算法变得更快。&rdquo;</p><p>Shrivastava 说，SLIDE 的重要性在于，它证明了还有其他方式来实现深度学习。</p><p>&ldquo;我们想要传达的整个信息是，&lsquo;我们不要被矩阵乘法和 GPU 内存这两个瓶颈所限制住，&rsquo; &rdquo; Chen 说，&ldquo;我们的算法可能是第一个击败 GPU 的算法，但我希望它不是最后一个。这个领域需要新的想法，而这正是这次 MLSys 机器学习系统会议的重要意义所在。&rdquo;</p><p>该算法的其他共同作者包括 James Farwell、Sameh Gobriel 和 Charlie Tai，他们都是来自英特尔实验室的成员。</p><p>该研究还得到了美国国家科学基金会（NSF-1652131, NSF-BIGDATA 1838177）、空军科研办公室（FA9550-18-1-0152）、亚马逊和海军研究办公室的支持。</p>', 0, '根据外媒报道，莱斯大学的计算机科学家们已经克服了人工智能产业迅速发展的一个主要障碍，他们证明了在不依赖于图形处理单元（GPU）等专业级加速硬件的情况下，也能够实现对深度学习技术的加速。这个名为 SLI', NULL, 'CPU 上运算比 GPU 还快？美国莱斯大学最新研究克服硬件障碍', 46, 9, 2, '2020-03-30 18:25:04', 2, '2020-04-04 00:43:04', 'admin', 'admin', 0, 'post', 1, 1);
COMMIT;

-- ----------------------------
-- Table structure for post_category_ref
-- ----------------------------
DROP TABLE IF EXISTS `post_category_ref`;
CREATE TABLE `post_category_ref` (
  `post_id` bigint(20) NOT NULL,
  `cate_id` bigint(20) NOT NULL,
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `FKatntuqmrfdi01vecyyi24arus` (`cate_id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of post_category_ref
-- ----------------------------
BEGIN;
INSERT INTO `post_category_ref` VALUES (14, 9, 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56', 16);
INSERT INTO `post_category_ref` VALUES (15, 11, 0, NULL, '2020-03-12 18:12:21', NULL, '2020-03-12 18:12:21', 17);
INSERT INTO `post_category_ref` VALUES (16, 10, 0, NULL, '2020-03-12 18:14:13', NULL, '2020-03-12 18:14:13', 19);
INSERT INTO `post_category_ref` VALUES (17, 14, 0, NULL, '2020-03-12 18:16:24', NULL, '2020-03-12 18:16:24', 21);
INSERT INTO `post_category_ref` VALUES (18, 9, 0, NULL, '2020-03-12 18:17:24', NULL, '2020-03-12 18:17:24', 22);
INSERT INTO `post_category_ref` VALUES (19, 9, 0, NULL, '2020-03-12 18:20:02', NULL, '2020-03-12 18:20:02', 23);
INSERT INTO `post_category_ref` VALUES (20, 9, 0, NULL, '2020-03-12 18:20:51', NULL, '2020-03-12 18:20:51', 24);
INSERT INTO `post_category_ref` VALUES (21, 11, 0, NULL, '2020-03-12 18:21:58', NULL, '2020-03-12 18:21:58', 25);
INSERT INTO `post_category_ref` VALUES (22, 10, 0, NULL, '2020-03-12 18:23:16', NULL, '2020-03-12 18:23:16', 26);
INSERT INTO `post_category_ref` VALUES (24, 13, 0, NULL, '2020-04-03 23:00:38', NULL, '2020-04-03 23:00:38', 29);
INSERT INTO `post_category_ref` VALUES (23, 9, 0, NULL, '2020-04-03 23:01:00', NULL, '2020-04-03 23:01:00', 30);
COMMIT;

-- ----------------------------
-- Table structure for post_tag_ref
-- ----------------------------
DROP TABLE IF EXISTS `post_tag_ref`;
CREATE TABLE `post_tag_ref` (
  `post_id` bigint(20) NOT NULL,
  `tag_id` bigint(20) NOT NULL,
  `del_flag` int(1) DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `FKix1v0nbpp1c84a3v9b917u9ii` (`tag_id`)
) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of post_tag_ref
-- ----------------------------
BEGIN;
INSERT INTO `post_tag_ref` VALUES (14, 37, 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56', 34);
INSERT INTO `post_tag_ref` VALUES (14, 38, 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56', 35);
INSERT INTO `post_tag_ref` VALUES (14, 39, 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56', 36);
INSERT INTO `post_tag_ref` VALUES (14, 40, 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56', 37);
INSERT INTO `post_tag_ref` VALUES (15, 41, 0, NULL, '2020-03-12 18:12:21', NULL, '2020-03-12 18:12:21', 38);
INSERT INTO `post_tag_ref` VALUES (15, 42, 0, NULL, '2020-03-12 18:12:21', NULL, '2020-03-12 18:12:21', 39);
INSERT INTO `post_tag_ref` VALUES (15, 43, 0, NULL, '2020-03-12 18:12:21', NULL, '2020-03-12 18:12:21', 40);
INSERT INTO `post_tag_ref` VALUES (16, 44, 0, NULL, '2020-03-12 18:14:13', NULL, '2020-03-12 18:14:13', 44);
INSERT INTO `post_tag_ref` VALUES (16, 45, 0, NULL, '2020-03-12 18:14:13', NULL, '2020-03-12 18:14:13', 45);
INSERT INTO `post_tag_ref` VALUES (16, 46, 0, NULL, '2020-03-12 18:14:13', NULL, '2020-03-12 18:14:13', 46);
INSERT INTO `post_tag_ref` VALUES (17, 47, 0, NULL, '2020-03-12 18:16:24', NULL, '2020-03-12 18:16:24', 48);
INSERT INTO `post_tag_ref` VALUES (18, 48, 0, NULL, '2020-03-12 18:17:24', NULL, '2020-03-12 18:17:24', 49);
INSERT INTO `post_tag_ref` VALUES (18, 49, 0, NULL, '2020-03-12 18:17:24', NULL, '2020-03-12 18:17:24', 50);
INSERT INTO `post_tag_ref` VALUES (19, 50, 0, NULL, '2020-03-12 18:20:02', NULL, '2020-03-12 18:20:02', 51);
INSERT INTO `post_tag_ref` VALUES (19, 51, 0, NULL, '2020-03-12 18:20:02', NULL, '2020-03-12 18:20:02', 52);
INSERT INTO `post_tag_ref` VALUES (20, 37, 0, NULL, '2020-03-12 18:20:51', NULL, '2020-03-12 18:20:51', 53);
INSERT INTO `post_tag_ref` VALUES (20, 52, 0, NULL, '2020-03-12 18:20:51', NULL, '2020-03-12 18:20:51', 54);
INSERT INTO `post_tag_ref` VALUES (20, 53, 0, NULL, '2020-03-12 18:20:51', NULL, '2020-03-12 18:20:51', 55);
INSERT INTO `post_tag_ref` VALUES (22, 56, 0, NULL, '2020-03-12 18:23:16', NULL, '2020-03-12 18:23:16', 59);
INSERT INTO `post_tag_ref` VALUES (22, 50, 0, NULL, '2020-03-12 18:23:16', NULL, '2020-03-12 18:23:16', 60);
INSERT INTO `post_tag_ref` VALUES (22, 57, 0, NULL, '2020-03-12 18:23:16', NULL, '2020-03-12 18:23:16', 61);
INSERT INTO `post_tag_ref` VALUES (24, 60, 0, NULL, '2020-04-03 23:00:41', NULL, '2020-04-03 23:00:41', 64);
INSERT INTO `post_tag_ref` VALUES (24, 61, 0, NULL, '2020-04-03 23:00:41', NULL, '2020-04-03 23:00:41', 65);
INSERT INTO `post_tag_ref` VALUES (23, 62, 0, NULL, '2020-04-03 23:01:00', NULL, '2020-04-03 23:01:00', 66);
INSERT INTO `post_tag_ref` VALUES (23, 58, 0, NULL, '2020-04-03 23:01:00', NULL, '2020-04-03 23:01:00', 67);
COMMIT;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role` varchar(100) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `level` int(1) NOT NULL,
  `del_flag` int(1) DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `is_register_default` int(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, 'admin', '管理员', 10, 0, NULL, '2020-02-05 18:54:23', NULL, '2020-03-08 13:31:39', 0);
INSERT INTO `role` VALUES (2, 'user', '用户', 5, 0, NULL, '2020-02-05 18:54:29', NULL, '2020-03-08 13:31:46', 1);
COMMIT;

-- ----------------------------
-- Table structure for role_permission_ref
-- ----------------------------
DROP TABLE IF EXISTS `role_permission_ref`;
CREATE TABLE `role_permission_ref` (
  `role_id` bigint(20) NOT NULL,
  `permission_id` bigint(20) NOT NULL,
  `del_flag` int(1) DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1810 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of role_permission_ref
-- ----------------------------
BEGIN;
INSERT INTO `role_permission_ref` VALUES (4, 1, 0, NULL, '2019-10-15 21:15:32', NULL, '2019-10-15 21:15:32', 421);
INSERT INTO `role_permission_ref` VALUES (4, 6, 0, NULL, '2019-10-15 21:15:32', NULL, '2019-10-15 21:15:32', 422);
INSERT INTO `role_permission_ref` VALUES (4, 70, 0, NULL, '2019-10-15 21:15:32', NULL, '2019-10-15 21:15:32', 429);
INSERT INTO `role_permission_ref` VALUES (4, 82, 0, NULL, '2019-10-15 21:15:32', NULL, '2019-10-15 21:15:32', 432);
INSERT INTO `role_permission_ref` VALUES (4, 83, 0, NULL, '2019-10-15 21:15:32', NULL, '2019-10-15 21:15:32', 433);
INSERT INTO `role_permission_ref` VALUES (5, 1, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 565);
INSERT INTO `role_permission_ref` VALUES (5, 6, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 569);
INSERT INTO `role_permission_ref` VALUES (5, 70, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 618);
INSERT INTO `role_permission_ref` VALUES (5, 72, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 620);
INSERT INTO `role_permission_ref` VALUES (5, 76, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 621);
INSERT INTO `role_permission_ref` VALUES (5, 91, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 626);
INSERT INTO `role_permission_ref` VALUES (5, 93, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 627);
INSERT INTO `role_permission_ref` VALUES (5, 95, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 628);
INSERT INTO `role_permission_ref` VALUES (5, 97, 0, NULL, '2019-10-15 22:53:09', NULL, '2019-10-15 22:53:09', 629);
INSERT INTO `role_permission_ref` VALUES (14, 1, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 861);
INSERT INTO `role_permission_ref` VALUES (14, 6, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 865);
INSERT INTO `role_permission_ref` VALUES (14, 106, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 867);
INSERT INTO `role_permission_ref` VALUES (14, 70, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 878);
INSERT INTO `role_permission_ref` VALUES (14, 72, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 880);
INSERT INTO `role_permission_ref` VALUES (14, 73, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 881);
INSERT INTO `role_permission_ref` VALUES (14, 74, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 882);
INSERT INTO `role_permission_ref` VALUES (14, 75, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 883);
INSERT INTO `role_permission_ref` VALUES (14, 76, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 884);
INSERT INTO `role_permission_ref` VALUES (14, 82, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 890);
INSERT INTO `role_permission_ref` VALUES (14, 83, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 891);
INSERT INTO `role_permission_ref` VALUES (14, 91, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 895);
INSERT INTO `role_permission_ref` VALUES (14, 92, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 896);
INSERT INTO `role_permission_ref` VALUES (14, 93, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 897);
INSERT INTO `role_permission_ref` VALUES (14, 94, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 898);
INSERT INTO `role_permission_ref` VALUES (14, 95, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 899);
INSERT INTO `role_permission_ref` VALUES (14, 96, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 900);
INSERT INTO `role_permission_ref` VALUES (14, 97, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 901);
INSERT INTO `role_permission_ref` VALUES (14, 98, 0, NULL, '2020-02-06 11:38:27', NULL, '2020-02-06 11:38:27', 902);
INSERT INTO `role_permission_ref` VALUES (15, 1, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 903);
INSERT INTO `role_permission_ref` VALUES (15, 6, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 907);
INSERT INTO `role_permission_ref` VALUES (15, 106, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 909);
INSERT INTO `role_permission_ref` VALUES (15, 70, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 920);
INSERT INTO `role_permission_ref` VALUES (15, 72, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 922);
INSERT INTO `role_permission_ref` VALUES (15, 73, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 923);
INSERT INTO `role_permission_ref` VALUES (15, 74, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 924);
INSERT INTO `role_permission_ref` VALUES (15, 75, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 925);
INSERT INTO `role_permission_ref` VALUES (15, 76, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 926);
INSERT INTO `role_permission_ref` VALUES (15, 82, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 932);
INSERT INTO `role_permission_ref` VALUES (15, 83, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 933);
INSERT INTO `role_permission_ref` VALUES (15, 91, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 937);
INSERT INTO `role_permission_ref` VALUES (15, 92, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 938);
INSERT INTO `role_permission_ref` VALUES (15, 93, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 939);
INSERT INTO `role_permission_ref` VALUES (15, 94, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 940);
INSERT INTO `role_permission_ref` VALUES (15, 95, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 941);
INSERT INTO `role_permission_ref` VALUES (15, 96, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 942);
INSERT INTO `role_permission_ref` VALUES (15, 97, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 943);
INSERT INTO `role_permission_ref` VALUES (15, 98, 0, NULL, '2020-02-06 11:40:38', NULL, '2020-02-06 11:40:38', 944);
INSERT INTO `role_permission_ref` VALUES (16, 1, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 945);
INSERT INTO `role_permission_ref` VALUES (16, 6, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 949);
INSERT INTO `role_permission_ref` VALUES (16, 106, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 951);
INSERT INTO `role_permission_ref` VALUES (16, 70, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 962);
INSERT INTO `role_permission_ref` VALUES (16, 72, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 964);
INSERT INTO `role_permission_ref` VALUES (16, 73, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 965);
INSERT INTO `role_permission_ref` VALUES (16, 74, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 966);
INSERT INTO `role_permission_ref` VALUES (16, 75, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 967);
INSERT INTO `role_permission_ref` VALUES (16, 76, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 968);
INSERT INTO `role_permission_ref` VALUES (16, 82, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 974);
INSERT INTO `role_permission_ref` VALUES (16, 83, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 975);
INSERT INTO `role_permission_ref` VALUES (16, 91, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 979);
INSERT INTO `role_permission_ref` VALUES (16, 92, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 980);
INSERT INTO `role_permission_ref` VALUES (16, 93, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 981);
INSERT INTO `role_permission_ref` VALUES (16, 94, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 982);
INSERT INTO `role_permission_ref` VALUES (16, 95, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 983);
INSERT INTO `role_permission_ref` VALUES (16, 96, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 984);
INSERT INTO `role_permission_ref` VALUES (16, 97, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 985);
INSERT INTO `role_permission_ref` VALUES (16, 98, 0, NULL, '2020-02-06 11:42:26', NULL, '2020-02-06 11:42:26', 986);
INSERT INTO `role_permission_ref` VALUES (3, 1, 0, NULL, '2020-02-08 14:20:52', NULL, '2020-02-08 14:20:52', 1170);
INSERT INTO `role_permission_ref` VALUES (3, 106, 0, NULL, '2020-02-08 14:20:52', NULL, '2020-02-08 14:20:52', 1171);
INSERT INTO `role_permission_ref` VALUES (3, 6, 0, NULL, '2020-02-08 14:20:52', NULL, '2020-02-08 14:20:52', 1172);
INSERT INTO `role_permission_ref` VALUES (3, 120, 0, NULL, '2020-02-08 14:20:52', NULL, '2020-02-08 14:20:52', 1174);
INSERT INTO `role_permission_ref` VALUES (3, 82, 0, NULL, '2020-02-08 14:20:52', NULL, '2020-02-08 14:20:52', 1175);
INSERT INTO `role_permission_ref` VALUES (3, 83, 0, NULL, '2020-02-08 14:20:52', NULL, '2020-02-08 14:20:52', 1176);
INSERT INTO `role_permission_ref` VALUES (13, 1, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1177);
INSERT INTO `role_permission_ref` VALUES (13, 106, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1178);
INSERT INTO `role_permission_ref` VALUES (13, 6, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1179);
INSERT INTO `role_permission_ref` VALUES (13, 70, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1181);
INSERT INTO `role_permission_ref` VALUES (13, 72, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1182);
INSERT INTO `role_permission_ref` VALUES (13, 73, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1183);
INSERT INTO `role_permission_ref` VALUES (13, 74, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1184);
INSERT INTO `role_permission_ref` VALUES (13, 75, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1185);
INSERT INTO `role_permission_ref` VALUES (13, 76, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1186);
INSERT INTO `role_permission_ref` VALUES (13, 120, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1187);
INSERT INTO `role_permission_ref` VALUES (13, 82, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1188);
INSERT INTO `role_permission_ref` VALUES (13, 83, 0, NULL, '2020-02-08 14:21:23', NULL, '2020-02-08 14:21:23', 1189);
INSERT INTO `role_permission_ref` VALUES (17, 1, 0, NULL, '2020-02-08 18:47:20', NULL, '2020-02-08 18:47:20', 1230);
INSERT INTO `role_permission_ref` VALUES (18, 1, 0, NULL, '2020-02-08 18:47:41', NULL, '2020-02-08 18:47:41', 1231);
INSERT INTO `role_permission_ref` VALUES (2, 1, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1729);
INSERT INTO `role_permission_ref` VALUES (2, 106, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1730);
INSERT INTO `role_permission_ref` VALUES (2, 6, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1731);
INSERT INTO `role_permission_ref` VALUES (2, 131, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1733);
INSERT INTO `role_permission_ref` VALUES (2, 133, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1734);
INSERT INTO `role_permission_ref` VALUES (2, 140, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1735);
INSERT INTO `role_permission_ref` VALUES (2, 141, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1736);
INSERT INTO `role_permission_ref` VALUES (2, 142, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1737);
INSERT INTO `role_permission_ref` VALUES (2, 144, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1738);
INSERT INTO `role_permission_ref` VALUES (2, 145, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1739);
INSERT INTO `role_permission_ref` VALUES (2, 132, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1740);
INSERT INTO `role_permission_ref` VALUES (2, 143, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1741);
INSERT INTO `role_permission_ref` VALUES (2, 146, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1742);
INSERT INTO `role_permission_ref` VALUES (2, 134, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1743);
INSERT INTO `role_permission_ref` VALUES (2, 157, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1744);
INSERT INTO `role_permission_ref` VALUES (2, 158, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1745);
INSERT INTO `role_permission_ref` VALUES (2, 159, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1746);
INSERT INTO `role_permission_ref` VALUES (2, 136, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1747);
INSERT INTO `role_permission_ref` VALUES (2, 135, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1748);
INSERT INTO `role_permission_ref` VALUES (2, 120, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1749);
INSERT INTO `role_permission_ref` VALUES (2, 82, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1750);
INSERT INTO `role_permission_ref` VALUES (2, 83, 0, NULL, '2020-03-11 22:12:39', NULL, '2020-03-11 22:12:39', 1751);
INSERT INTO `role_permission_ref` VALUES (1, 1, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1752);
INSERT INTO `role_permission_ref` VALUES (1, 106, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1753);
INSERT INTO `role_permission_ref` VALUES (1, 6, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1754);
INSERT INTO `role_permission_ref` VALUES (1, 131, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1755);
INSERT INTO `role_permission_ref` VALUES (1, 133, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1756);
INSERT INTO `role_permission_ref` VALUES (1, 140, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1757);
INSERT INTO `role_permission_ref` VALUES (1, 141, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1758);
INSERT INTO `role_permission_ref` VALUES (1, 142, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1759);
INSERT INTO `role_permission_ref` VALUES (1, 144, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1760);
INSERT INTO `role_permission_ref` VALUES (1, 145, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1761);
INSERT INTO `role_permission_ref` VALUES (1, 160, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1762);
INSERT INTO `role_permission_ref` VALUES (1, 161, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1763);
INSERT INTO `role_permission_ref` VALUES (1, 162, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1764);
INSERT INTO `role_permission_ref` VALUES (1, 163, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1765);
INSERT INTO `role_permission_ref` VALUES (1, 132, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1766);
INSERT INTO `role_permission_ref` VALUES (1, 143, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1767);
INSERT INTO `role_permission_ref` VALUES (1, 146, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1768);
INSERT INTO `role_permission_ref` VALUES (1, 137, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1769);
INSERT INTO `role_permission_ref` VALUES (1, 138, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1770);
INSERT INTO `role_permission_ref` VALUES (1, 148, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1771);
INSERT INTO `role_permission_ref` VALUES (1, 149, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1772);
INSERT INTO `role_permission_ref` VALUES (1, 139, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1773);
INSERT INTO `role_permission_ref` VALUES (1, 147, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1774);
INSERT INTO `role_permission_ref` VALUES (1, 150, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1775);
INSERT INTO `role_permission_ref` VALUES (1, 151, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1776);
INSERT INTO `role_permission_ref` VALUES (1, 152, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1777);
INSERT INTO `role_permission_ref` VALUES (1, 155, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1778);
INSERT INTO `role_permission_ref` VALUES (1, 154, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1779);
INSERT INTO `role_permission_ref` VALUES (1, 156, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1780);
INSERT INTO `role_permission_ref` VALUES (1, 134, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1781);
INSERT INTO `role_permission_ref` VALUES (1, 153, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1782);
INSERT INTO `role_permission_ref` VALUES (1, 157, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1783);
INSERT INTO `role_permission_ref` VALUES (1, 158, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1784);
INSERT INTO `role_permission_ref` VALUES (1, 159, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1785);
INSERT INTO `role_permission_ref` VALUES (1, 136, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1786);
INSERT INTO `role_permission_ref` VALUES (1, 135, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1787);
INSERT INTO `role_permission_ref` VALUES (1, 70, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1788);
INSERT INTO `role_permission_ref` VALUES (1, 126, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1789);
INSERT INTO `role_permission_ref` VALUES (1, 72, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1790);
INSERT INTO `role_permission_ref` VALUES (1, 73, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1791);
INSERT INTO `role_permission_ref` VALUES (1, 74, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1792);
INSERT INTO `role_permission_ref` VALUES (1, 75, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1793);
INSERT INTO `role_permission_ref` VALUES (1, 76, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1794);
INSERT INTO `role_permission_ref` VALUES (1, 91, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1795);
INSERT INTO `role_permission_ref` VALUES (1, 127, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1796);
INSERT INTO `role_permission_ref` VALUES (1, 111, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1797);
INSERT INTO `role_permission_ref` VALUES (1, 92, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1798);
INSERT INTO `role_permission_ref` VALUES (1, 93, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1799);
INSERT INTO `role_permission_ref` VALUES (1, 94, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1800);
INSERT INTO `role_permission_ref` VALUES (1, 95, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1801);
INSERT INTO `role_permission_ref` VALUES (1, 128, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1802);
INSERT INTO `role_permission_ref` VALUES (1, 110, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1803);
INSERT INTO `role_permission_ref` VALUES (1, 96, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1804);
INSERT INTO `role_permission_ref` VALUES (1, 97, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1805);
INSERT INTO `role_permission_ref` VALUES (1, 98, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1806);
INSERT INTO `role_permission_ref` VALUES (1, 120, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1807);
INSERT INTO `role_permission_ref` VALUES (1, 82, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1808);
INSERT INTO `role_permission_ref` VALUES (1, 83, 0, NULL, '2020-04-03 22:57:33', NULL, '2020-04-03 22:57:33', 1809);
COMMIT;

-- ----------------------------
-- Table structure for tag
-- ----------------------------
DROP TABLE IF EXISTS `tag`;
CREATE TABLE `tag` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `tag_name` varchar(100) NOT NULL,
  `del_flag` int(1) NOT NULL DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of tag
-- ----------------------------
BEGIN;
INSERT INTO `tag` VALUES (36, '视频', 0, NULL, '2020-03-11 21:47:11', NULL, '2020-03-11 21:47:11');
INSERT INTO `tag` VALUES (37, 'Java', 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56');
INSERT INTO `tag` VALUES (38, '安卓', 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56');
INSERT INTO `tag` VALUES (39, 'Kotlin', 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56');
INSERT INTO `tag` VALUES (40, '谷歌', 0, NULL, '2020-03-12 18:10:56', NULL, '2020-03-12 18:10:56');
INSERT INTO `tag` VALUES (41, 'AI', 0, NULL, '2020-03-12 18:12:20', NULL, '2020-03-12 18:12:20');
INSERT INTO `tag` VALUES (42, '人脸识别', 0, NULL, '2020-03-12 18:12:20', NULL, '2020-03-12 18:12:20');
INSERT INTO `tag` VALUES (43, '人脸搜索', 0, NULL, '2020-03-12 18:12:21', NULL, '2020-03-12 18:12:21');
INSERT INTO `tag` VALUES (44, 'Oracle', 0, NULL, '2020-03-12 18:13:52', NULL, '2020-03-12 18:13:52');
INSERT INTO `tag` VALUES (45, '金融', 0, NULL, '2020-03-12 18:13:52', NULL, '2020-03-12 18:13:52');
INSERT INTO `tag` VALUES (46, '陆金所', 0, NULL, '2020-03-12 18:13:52', NULL, '2020-03-12 18:13:52');
INSERT INTO `tag` VALUES (47, '算法', 0, NULL, '2020-03-12 18:15:41', NULL, '2020-03-12 18:15:41');
INSERT INTO `tag` VALUES (48, 'Python', 0, NULL, '2020-03-12 18:17:24', NULL, '2020-03-12 18:17:24');
INSERT INTO `tag` VALUES (49, '工具', 0, NULL, '2020-03-12 18:17:24', NULL, '2020-03-12 18:17:24');
INSERT INTO `tag` VALUES (50, '大数据', 0, NULL, '2020-03-12 18:20:02', NULL, '2020-03-12 18:20:02');
INSERT INTO `tag` VALUES (51, '高并发', 0, NULL, '2020-03-12 18:20:02', NULL, '2020-03-12 18:20:02');
INSERT INTO `tag` VALUES (52, '后端开发', 0, NULL, '2020-03-12 18:20:51', NULL, '2020-03-12 18:20:51');
INSERT INTO `tag` VALUES (53, '序列化', 0, NULL, '2020-03-12 18:20:51', NULL, '2020-03-12 18:20:51');
INSERT INTO `tag` VALUES (54, '机器学习', 0, NULL, '2020-03-12 18:21:57', NULL, '2020-03-12 18:21:57');
INSERT INTO `tag` VALUES (55, 'TensorFlow', 0, NULL, '2020-03-12 18:21:57', NULL, '2020-03-12 18:21:57');
INSERT INTO `tag` VALUES (56, '腾讯', 0, NULL, '2020-03-12 18:23:16', NULL, '2020-03-12 18:23:16');
INSERT INTO `tag` VALUES (57, '开源', 0, NULL, '2020-03-12 18:23:16', NULL, '2020-03-12 18:23:16');
INSERT INTO `tag` VALUES (58, 'Druid', 0, NULL, '2020-03-12 18:24:02', NULL, '2020-03-12 18:24:02');
INSERT INTO `tag` VALUES (59, '推特', 0, NULL, '2020-03-12 18:24:02', NULL, '2020-03-12 18:24:02');
INSERT INTO `tag` VALUES (60, 'CPU', 0, NULL, '2020-03-12 18:25:04', NULL, '2020-03-12 18:25:04');
INSERT INTO `tag` VALUES (61, 'GPU', 0, NULL, '2020-03-12 18:25:04', NULL, '2020-03-12 18:25:04');
INSERT INTO `tag` VALUES (62, 'Twitter', 0, NULL, '2020-04-03 23:01:00', NULL, '2020-04-03 23:01:00');
COMMIT;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `login_enable` varchar(10) DEFAULT NULL,
  `login_error` int(11) DEFAULT NULL,
  `login_last` datetime DEFAULT NULL,
  `user_avatar` varchar(255) DEFAULT NULL,
  `user_desc` varchar(255) DEFAULT NULL,
  `user_display_name` varchar(255) DEFAULT NULL,
  `user_email` varchar(100) DEFAULT NULL,
  `user_name` varchar(100) DEFAULT NULL,
  `user_pass` varchar(255) DEFAULT NULL,
  `status` int(1) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_by` varchar(255) DEFAULT 'admin',
  `update_by` varchar(255) DEFAULT 'admin',
  `del_flag` int(1) DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'true', 0, '2020-04-04 11:13:50', '/static/images/avatar/1.jpeg', '人生得意须尽欢', '管理员', '111111@qq.com', 'admin', 'a021a665f503979c06f50b8de66a4218', 0, '2019-01-24 00:07:33', '2020-03-11 17:44:15', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (2, 'true', 0, NULL, '/static/images/avatar/2.jpeg', '11', '马云', '123@qq.com', 'mayun', 'a021a665f503979c06f50b8de66a4218', 0, '2020-02-05 17:37:43', '2020-02-08 20:33:24', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (3, 'true', 0, '2020-04-03 21:42:44', '/static/images/avatar/3.jpeg', '', '张三', '121113@qq.com', 'zhangsan', 'a021a665f503979c06f50b8de66a4218', 0, '2020-02-08 13:22:22', '2020-02-08 20:33:26', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (4, 'true', 0, '2020-02-16 12:57:48', '/static/images/avatar/6.jpeg', '', '李四', 'lisi@qq.com', 'lisi', 'a021a665f503979c06f50b8de66a4218', 0, '2020-02-08 13:57:51', '2020-03-07 18:00:42', 'admin', 'admin', 1);
INSERT INTO `user` VALUES (5, 'true', 0, NULL, '/static/images/avatar/4.jpeg', '1111', '11', '12111113@qq.com', '111111', 'a021a665f503979c06f50b8de66a4218', 0, '2020-02-08 18:48:20', '2020-03-07 18:00:44', 'admin', 'admin', 1);
INSERT INTO `user` VALUES (6, 'true', 0, '2020-02-08 18:54:29', '/static/images/avatar/5.jpeg', '', '黄忠', '1231111@qq.com', 'huang', 'a021a665f503979c06f50b8de66a4218', 0, '2020-02-08 18:54:21', '2020-02-08 20:33:33', 'admin', 'admin', 1);
INSERT INTO `user` VALUES (7, 'true', 0, '2020-03-08 14:21:48', '/static/images/avatar/1.jpeg', '', 'mayun2', '123456@mayun.com', 'mayun2', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-08 14:21:05', '2020-03-08 14:21:05', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (8, 'true', 0, '2020-03-09 16:21:31', '/static/images/avatar/35.jpeg', NULL, 'mahuateng', '111@qq.com', 'mahuateng', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-09 13:24:42', '2020-03-09 13:24:42', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (9, 'true', 0, '2020-03-14 15:20:43', '/static/images/avatar/17.jpeg', NULL, 'zhaoyun', '847064370@qq.com', 'zhaoyun', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-11 21:27:11', '2020-03-12 11:31:43', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (10, 'true', 0, '2020-03-11 21:41:01', '/static/images/avatar/28.jpeg', NULL, 'wangwu', 'wangwu@qq.com', 'wangwu', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-11 21:38:51', '2020-03-11 21:38:51', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (11, 'true', 0, NULL, '/static/images/avatar/25.jpeg', NULL, 'wangwu2', '1234562@mayun.com', 'wangwu2', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-11 21:40:54', '2020-03-11 21:40:54', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (12, 'true', 0, '2020-03-11 21:42:04', '/static/images/avatar/13.jpeg', NULL, 'zhangfei', '123456@zhang.com', 'zhangfei', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-11 21:41:55', '2020-03-11 21:41:55', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (13, 'true', 0, '2020-03-11 21:44:48', '/static/images/avatar/9.jpeg', NULL, 'liubei', '123@qqq.com', 'liubei', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-11 21:42:51', '2020-03-11 21:42:51', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (14, 'true', 0, NULL, '/static/images/avatar/13.jpeg', NULL, 'liubei2', '123456@m22ayun.com', 'liubei2', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-11 21:44:44', '2020-03-11 21:44:44', 'admin', 'admin', 0);
INSERT INTO `user` VALUES (16, 'true', 0, '2020-03-12 18:19:01', '/static/images/avatar/34.jpeg', NULL, 'lisi', 'lisi@qq.com', 'lisi', 'a021a665f503979c06f50b8de66a4218', 0, '2020-03-12 18:18:57', '2020-03-12 18:18:57', 'admin', 'admin', 0);
COMMIT;

-- ----------------------------
-- Table structure for user_role_ref
-- ----------------------------
DROP TABLE IF EXISTS `user_role_ref`;
CREATE TABLE `user_role_ref` (
  `user_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  `del_flag` int(1) DEFAULT '0',
  `create_by` varchar(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT NULL,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of user_role_ref
-- ----------------------------
BEGIN;
INSERT INTO `user_role_ref` VALUES (2, 2, 0, NULL, '2020-02-05 17:37:43', NULL, '2020-02-05 17:37:43', 2);
INSERT INTO `user_role_ref` VALUES (1, 1, 0, NULL, '2020-02-08 13:56:55', NULL, '2020-02-08 13:56:55', 4);
INSERT INTO `user_role_ref` VALUES (3, 2, 0, NULL, '2020-02-08 18:53:44', NULL, '2020-02-08 18:53:44', 10);
INSERT INTO `user_role_ref` VALUES (7, 2, 0, NULL, '2020-03-08 14:21:05', NULL, '2020-03-08 14:21:05', 11);
INSERT INTO `user_role_ref` VALUES (8, 2, 0, NULL, '2020-03-09 13:24:42', NULL, '2020-03-09 13:24:42', 12);
INSERT INTO `user_role_ref` VALUES (9, 2, 0, NULL, '2020-03-11 21:27:11', NULL, '2020-03-11 21:27:11', 13);
INSERT INTO `user_role_ref` VALUES (10, 2, 0, NULL, '2020-03-11 21:38:51', NULL, '2020-03-11 21:38:51', 14);
INSERT INTO `user_role_ref` VALUES (11, 2, 0, NULL, '2020-03-11 21:40:54', NULL, '2020-03-11 21:40:54', 15);
INSERT INTO `user_role_ref` VALUES (12, 2, 0, NULL, '2020-03-11 21:41:56', NULL, '2020-03-11 21:41:56', 16);
INSERT INTO `user_role_ref` VALUES (13, 2, 0, NULL, '2020-03-11 21:42:51', NULL, '2020-03-11 21:42:51', 17);
INSERT INTO `user_role_ref` VALUES (14, 2, 0, NULL, '2020-03-11 21:44:44', NULL, '2020-03-11 21:44:44', 18);
INSERT INTO `user_role_ref` VALUES (16, 2, 0, NULL, '2020-03-12 18:18:57', NULL, '2020-03-12 18:18:57', 19);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
